Skip to content

Commit c20ddd2

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

File tree

2 files changed

+143
-2
lines changed

2 files changed

+143
-2
lines changed

ext/pdo/pdo_stmt.c

Lines changed: 5 additions & 2 deletions
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
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
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 pdo_stmt_cyclic_ref(id INT NOT NULL PRIMARY KEY, val VARCHAR(10), val2 VARCHAR(10))');
35+
$db->exec("INSERT INTO pdo_stmt_cyclic_ref VALUES(1, 'A', 'AA')");
36+
$db->exec("INSERT INTO pdo_stmt_cyclic_ref VALUES(2, 'B', 'BB')");
37+
$db->exec("INSERT INTO pdo_stmt_cyclic_ref 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 pdo_stmt_cyclic_ref');
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 pdo_stmt_cyclic_ref');
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 pdo_stmt_cyclic_ref');
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+
--CLEAN--
67+
<?php
68+
require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
69+
$db = PDOTest::factory();
70+
PDOTest::dropTableIfExists($db, "pdo_stmt_cyclic_ref");
71+
?>
72+
--EXPECTF--
73+
Column fetch:
74+
string(1) "A"
75+
string(1) "B"
76+
string(1) "C"
77+
Class fetch:
78+
object(TestRow)#%d (4) {
79+
["id"]=>
80+
string(1) "1"
81+
["val"]=>
82+
string(1) "A"
83+
["val2"]=>
84+
string(2) "AA"
85+
["arg"]=>
86+
string(11) "Hello world"
87+
}
88+
object(TestRow)#%d (4) {
89+
["id"]=>
90+
string(1) "2"
91+
["val"]=>
92+
string(1) "B"
93+
["val2"]=>
94+
string(2) "BB"
95+
["arg"]=>
96+
string(11) "Hello world"
97+
}
98+
object(TestRow)#%d (4) {
99+
["id"]=>
100+
string(1) "3"
101+
["val"]=>
102+
string(1) "C"
103+
["val2"]=>
104+
string(2) "CC"
105+
["arg"]=>
106+
string(11) "Hello world"
107+
}
108+
Fetch into:
109+
object(TestRow)#4 (4) {
110+
["id"]=>
111+
string(1) "1"
112+
["val"]=>
113+
string(1) "A"
114+
["val2"]=>
115+
string(2) "AA"
116+
["arg"]=>
117+
string(21) "I am being fetch into"
118+
}
119+
object(TestRow)#4 (4) {
120+
["id"]=>
121+
string(1) "2"
122+
["val"]=>
123+
string(1) "B"
124+
["val2"]=>
125+
string(2) "BB"
126+
["arg"]=>
127+
string(21) "I am being fetch into"
128+
}
129+
object(TestRow)#4 (4) {
130+
["id"]=>
131+
string(1) "3"
132+
["val"]=>
133+
string(1) "C"
134+
["val2"]=>
135+
string(2) "CC"
136+
["arg"]=>
137+
string(21) "I am being fetch into"
138+
}

0 commit comments

Comments
 (0)