]> granicus.if.org Git - php/commitdiff
Fix bug #78192 PDO SQLite SegFault when reuse statement after schema has changed
authorVincent <vquatrevieux@b2pweb.com>
Wed, 26 Jun 2019 09:37:08 +0000 (11:37 +0200)
committerChristoph M. Becker <cmbecker69@gmx.de>
Fri, 28 Jun 2019 10:36:02 +0000 (12:36 +0200)
Reset stmt->columns when column count changed on new execution of prepared statement

NEWS
ext/pdo_sqlite/sqlite_statement.c
ext/pdo_sqlite/tests/bug78192.phpt [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index 14b7b612c7ddd2f3d8562f75d56ed5e35babcfca..8c2a6c2d22a86f7cde7ac3a3658a4e180a83bd16 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,10 @@ PHP                                                                        NEWS
 |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
 ?? ??? 2019, PHP 7.2.21
 
+- PDO_Sqlite:
+  . Fixed #78192 (SegFault when reuse statement after schema has changed).
+    (Vincent Quatrevieux)
+
 - XMLRPC:
   . Fixed #78173 (XML-RPC mutates immutable objects during encoding). (Asher
     Baker)
index 4437bbbde03f7bb1ca5e70c65b869ac161eff8c4..58811aee58b4ef1eabcd4db0d4e0c577e4fd306e 100644 (file)
@@ -43,6 +43,46 @@ static int pdo_sqlite_stmt_dtor(pdo_stmt_t *stmt)
        return 1;
 }
 
+/**
+ * Change the column count on the statement.
+ *
+ * Since PHP 7.2 sqlite3_prepare_v2 is used which auto recompile prepared statement on schema change.
+ * Instead of raise an error on schema change, the result set will change, and the statement's columns must be updated.
+ *
+ * See bug #78192
+ */
+static void pdo_sqlite_stmt_set_column_count(pdo_stmt_t *stmt, int new_count)
+{
+       /* Columns not yet "described" */
+       if (!stmt->columns) {
+               stmt->column_count = new_count;
+
+               return;
+       }
+
+       /*
+        * The column count has not changed : no need to reload columns description 
+        * Note: Do not handle attribute name change, without column count change
+        */
+       if (new_count == stmt->column_count) {
+               return;
+       }
+
+       /* Free previous columns to force reload description */
+       int i;
+
+       for (i = 0; i < stmt->column_count; i++) {
+               if (stmt->columns[i].name) {
+                       zend_string_release(stmt->columns[i].name);
+                       stmt->columns[i].name = NULL;
+               }
+       }
+
+       efree(stmt->columns);
+       stmt->columns = NULL;
+       stmt->column_count = new_count;
+}
+
 static int pdo_sqlite_stmt_execute(pdo_stmt_t *stmt)
 {
        pdo_sqlite_stmt *S = (pdo_sqlite_stmt*)stmt->driver_data;
@@ -55,11 +95,11 @@ static int pdo_sqlite_stmt_execute(pdo_stmt_t *stmt)
        switch (sqlite3_step(S->stmt)) {
                case SQLITE_ROW:
                        S->pre_fetched = 1;
-                       stmt->column_count = sqlite3_data_count(S->stmt);
+                       pdo_sqlite_stmt_set_column_count(stmt, sqlite3_data_count(S->stmt));
                        return 1;
 
                case SQLITE_DONE:
-                       stmt->column_count = sqlite3_column_count(S->stmt);
+                       pdo_sqlite_stmt_set_column_count(stmt, sqlite3_column_count(S->stmt));
                        stmt->row_count = sqlite3_changes(S->H->db);
                        sqlite3_reset(S->stmt);
                        S->done = 1;
diff --git a/ext/pdo_sqlite/tests/bug78192.phpt b/ext/pdo_sqlite/tests/bug78192.phpt
new file mode 100644 (file)
index 0000000..dcf4b74
--- /dev/null
@@ -0,0 +1,46 @@
+--TEST--
+PDO SQLite Bug #78192 SegFault when reuse statement after schema change
+--SKIPIF--
+<?php
+if (!extension_loaded('pdo_sqlite')) print 'skip not loaded';
+?>
+--FILE--
+<?php
+$connection = new \PDO('sqlite::memory:');
+$connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
+$connection->query('CREATE TABLE user (id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(255) NOT NULL)');
+
+$stmt = $connection->prepare('INSERT INTO user (id, name) VALUES(:id, :name)');
+$stmt->execute([
+    'id'   => 10,
+    'name' => 'test',
+]);
+
+$stmt = $connection->prepare('SELECT * FROM user WHERE id = :id');
+$stmt->execute(['id' => 10]);
+var_dump($stmt->fetchAll(\PDO::FETCH_ASSOC));
+
+$connection->query('ALTER TABLE user ADD new_col VARCHAR(255)');
+$stmt->execute(['id' => 10]);
+var_dump($stmt->fetchAll(\PDO::FETCH_ASSOC));
+--EXPECT--
+array(1) {
+  [0]=>
+  array(2) {
+    ["id"]=>
+    string(2) "10"
+    ["name"]=>
+    string(4) "test"
+  }
+}
+array(1) {
+  [0]=>
+  array(3) {
+    ["id"]=>
+    string(2) "10"
+    ["name"]=>
+    string(4) "test"
+    ["new_col"]=>
+    NULL
+  }
+}