Skip to content

Commit 99f8ec3

Browse files
committed
ext/pdo: Fix memory leak if GC needs to free PDO Statement
1 parent 7068357 commit 99f8ec3

File tree

2 files changed

+137
-2
lines changed

2 files changed

+137
-2
lines changed

ext/pdo/pdo_stmt.c

+5-2
Original file line numberDiff line numberDiff line change
@@ -2079,8 +2079,11 @@ static zend_function *dbstmt_method_get(zend_object **object_pp, zend_string *me
20792079
static HashTable *dbstmt_get_gc(zend_object *object, zval **gc_data, int *gc_count)
20802080
{
20812081
pdo_stmt_t *stmt = php_pdo_stmt_fetch_object(object);
2082-
*gc_data = &stmt->fetch.into;
2083-
*gc_count = 1;
2082+
2083+
zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create();
2084+
zend_get_gc_buffer_add_zval(gc_buffer, &stmt->database_object_handle);
2085+
zend_get_gc_buffer_add_zval(gc_buffer, &stmt->fetch.into);
2086+
zend_get_gc_buffer_use(gc_buffer, gc_data, gc_count);
20842087

20852088
/**
20862089
* If there are no dynamic properties and the default property is 1 (that is, there is only one property
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
--TEST--
2+
PDO Common: Cyclic PDOStatement child class
3+
--EXTENSIONS--
4+
pdo
5+
--SKIPIF--
6+
<?php
7+
$dir = getenv('REDIR_TEST_DIR');
8+
if (false == $dir) die('skip no driver');
9+
require_once $dir . 'pdo_test.inc';
10+
PDOTest::skip();
11+
?>
12+
--FILE--
13+
<?php
14+
if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.__DIR__ . '/../../pdo/tests/');
15+
require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
16+
17+
class Ref {
18+
public CyclicStatement $stmt;
19+
}
20+
21+
class CyclicStatement extends PDOStatement {
22+
protected function __construct(public Ref $ref) {}
23+
}
24+
25+
class TestRow {
26+
public $id;
27+
public $val;
28+
public $val2;
29+
30+
public function __construct(public string $arg) {}
31+
}
32+
33+
$db = PDOTest::factory();
34+
$db->exec('CREATE TABLE test(id INT NOT NULL PRIMARY KEY, val VARCHAR(10), val2 VARCHAR(10))');
35+
$db->exec("INSERT INTO test VALUES(1, 'A', 'AA')");
36+
$db->exec("INSERT INTO test VALUES(2, 'B', 'BB')");
37+
$db->exec("INSERT INTO test VALUES(3, 'C', 'CC')");
38+
39+
$db->setAttribute(PDO::ATTR_STATEMENT_CLASS, ['CyclicStatement', [new Ref]]);
40+
41+
echo "Column fetch:\n";
42+
$stmt = $db->query('SELECT id, val2, val FROM test');
43+
$stmt->ref->stmt = $stmt;
44+
$stmt->setFetchMode(PDO::FETCH_COLUMN, 2);
45+
foreach($stmt as $obj) {
46+
var_dump($obj);
47+
}
48+
49+
echo "Class fetch:\n";
50+
$stmt = $db->query('SELECT id, val2, val FROM test');
51+
$stmt->ref->stmt = $stmt;
52+
$stmt->setFetchMode(PDO::FETCH_CLASS, 'TestRow', ['Hello world']);
53+
foreach($stmt as $obj) {
54+
var_dump($obj);
55+
}
56+
57+
echo "Fetch into:\n";
58+
$stmt = $db->query('SELECT id, val2, val FROM test');
59+
$stmt->ref->stmt = $stmt;
60+
$stmt->setFetchMode(PDO::FETCH_INTO, new TestRow('I am being fetch into'));
61+
foreach($stmt as $obj) {
62+
var_dump($obj);
63+
}
64+
65+
?>
66+
--EXPECTF--
67+
Column fetch:
68+
string(1) "A"
69+
string(1) "B"
70+
string(1) "C"
71+
Class fetch:
72+
object(TestRow)#%d (4) {
73+
["id"]=>
74+
string(1) "1"
75+
["val"]=>
76+
string(1) "A"
77+
["val2"]=>
78+
string(2) "AA"
79+
["arg"]=>
80+
string(11) "Hello world"
81+
}
82+
object(TestRow)#%d (4) {
83+
["id"]=>
84+
string(1) "2"
85+
["val"]=>
86+
string(1) "B"
87+
["val2"]=>
88+
string(2) "BB"
89+
["arg"]=>
90+
string(11) "Hello world"
91+
}
92+
object(TestRow)#%d (4) {
93+
["id"]=>
94+
string(1) "3"
95+
["val"]=>
96+
string(1) "C"
97+
["val2"]=>
98+
string(2) "CC"
99+
["arg"]=>
100+
string(11) "Hello world"
101+
}
102+
Fetch into:
103+
object(TestRow)#4 (4) {
104+
["id"]=>
105+
string(1) "1"
106+
["val"]=>
107+
string(1) "A"
108+
["val2"]=>
109+
string(2) "AA"
110+
["arg"]=>
111+
string(21) "I am being fetch into"
112+
}
113+
object(TestRow)#4 (4) {
114+
["id"]=>
115+
string(1) "2"
116+
["val"]=>
117+
string(1) "B"
118+
["val2"]=>
119+
string(2) "BB"
120+
["arg"]=>
121+
string(21) "I am being fetch into"
122+
}
123+
object(TestRow)#4 (4) {
124+
["id"]=>
125+
string(1) "3"
126+
["val"]=>
127+
string(1) "C"
128+
["val2"]=>
129+
string(2) "CC"
130+
["arg"]=>
131+
string(21) "I am being fetch into"
132+
}

0 commit comments

Comments
 (0)