Skip to content

Commit 99f1f38

Browse files
committed
ext/pdo: Report bound params and columns to GC
PDOStatement's get_gc handler never reported bound_params or bound_columns, so reference cycles through a bindParam()/bindColumn() variable or its driver options stayed invisible to the collector and were never reclaimed. Report the parameter and driver_params zvals of both tables.
1 parent 22237f2 commit 99f1f38

3 files changed

Lines changed: 60 additions & 0 deletions

File tree

‎NEWS‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ PHP NEWS
108108
"array given" regardless of the value passed. (Ilia Alshanetsky)
109109
. Fixed PDOStatement::bindParam() and bindColumn() leaking the driver
110110
options value. (Ilia Alshanetsky)
111+
. Fixed PDOStatement not reporting its bound parameters and columns to the
112+
cycle collector. (Ilia Alshanetsky)
111113

112114
- PDO_Firebird:
113115
. Fixed bug GH-23758 (PDO_Firebird returns null for non-null empty BLOBs).

‎ext/pdo/pdo_stmt.c‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2108,6 +2108,22 @@ static HashTable *dbstmt_get_gc(zend_object *object, zval **gc_data, int *gc_cou
21082108
zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create();
21092109
zend_get_gc_buffer_add_zval(gc_buffer, &stmt->database_object_handle);
21102110
zend_get_gc_buffer_add_zval(gc_buffer, &stmt->fetch.into);
2111+
if (stmt->bound_params) {
2112+
zval *val;
2113+
ZEND_HASH_FOREACH_VAL(stmt->bound_params, val) {
2114+
struct pdo_bound_param_data *param = Z_PTR_P(val);
2115+
zend_get_gc_buffer_add_zval(gc_buffer, &param->parameter);
2116+
zend_get_gc_buffer_add_zval(gc_buffer, &param->driver_params);
2117+
} ZEND_HASH_FOREACH_END();
2118+
}
2119+
if (stmt->bound_columns) {
2120+
zval *val;
2121+
ZEND_HASH_FOREACH_VAL(stmt->bound_columns, val) {
2122+
struct pdo_bound_param_data *param = Z_PTR_P(val);
2123+
zend_get_gc_buffer_add_zval(gc_buffer, &param->parameter);
2124+
zend_get_gc_buffer_add_zval(gc_buffer, &param->driver_params);
2125+
} ZEND_HASH_FOREACH_END();
2126+
}
21112127
zend_get_gc_buffer_use(gc_buffer, gc_data, gc_count);
21122128

21132129
/**
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
--TEST--
2+
PDOStatement::get_gc() must report bound params and columns for cycle collection
3+
--EXTENSIONS--
4+
pdo
5+
pdo_sqlite
6+
--FILE--
7+
<?php
8+
class Tracked {
9+
public $stmt;
10+
function __destruct() {
11+
echo "collected\n";
12+
}
13+
}
14+
$db = new PDO('sqlite::memory:');
15+
$db->exec('CREATE TABLE test(a INT)');
16+
$stmt = $db->prepare('INSERT INTO test VALUES (?)');
17+
$stmt->execute([1]);
18+
$stmt2 = $db->prepare('SELECT a FROM test');
19+
$stmt2->execute();
20+
$stmt2->fetch(PDO::FETCH_ASSOC);
21+
for ($i = 0; $i < 3; $i++) {
22+
$val = 1;
23+
$tracked = new Tracked();
24+
$tracked->stmt = $stmt;
25+
$stmt->bindParam(1, $val, PDO::PARAM_INT, 0, $tracked);
26+
$col = null;
27+
$tracked2 = new Tracked();
28+
$tracked2->stmt = $stmt2;
29+
$stmt2->bindColumn('a', $col, PDO::PARAM_STR, 0, $tracked2);
30+
unset($tracked, $tracked2);
31+
}
32+
unset($stmt, $stmt2, $db);
33+
gc_collect_cycles();
34+
echo "end\n";
35+
--EXPECT--
36+
collected
37+
collected
38+
collected
39+
collected
40+
collected
41+
collected
42+
end

0 commit comments

Comments
 (0)