diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index c2816d72cf1..4c8ec23b158 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -427,7 +427,7 @@ jobs: -d zend_extension=opcache.so -d opcache.enable_cli=1 - uses: codecov/codecov-action@v4 - if: !cancelled() + if: ${{ !cancelled() }} with: fail_ci_if_error: true token: ${{ secrets.CODECOV_TOKEN }} @@ -500,7 +500,7 @@ jobs: echo opcache.jit_hot_side_exit=1 >> /etc/php.d/opcache.ini php -v - name: Test AMPHP - if: !cancelled() + if: ${{ !cancelled() }} run: | repositories="amp cache dns file http parallel parser pipeline process serialization socket sync websocket-client websocket-server" X=0 @@ -518,7 +518,7 @@ jobs: done exit $X - name: Test Laravel - if: !cancelled() + if: ${{ !cancelled() }} run: | git clone https://github.com/laravel/framework.git --branch=master --depth=1 cd framework @@ -531,7 +531,7 @@ jobs: exit 1 fi - name: Test ReactPHP - if: !cancelled() + if: ${{ !cancelled() }} run: | repositories="async cache child-process datagram dns event-loop promise promise-stream promise-timer stream" X=0 @@ -549,7 +549,7 @@ jobs: done exit $X - name: Test Revolt PHP - if: !cancelled() + if: ${{ !cancelled() }} run: | git clone https://github.com/revoltphp/event-loop.git --depth=1 cd event-loop @@ -560,7 +560,7 @@ jobs: exit 1 fi - name: Test Symfony - if: !cancelled() && !inputs.skip_symfony + if: ${{ !cancelled() && !inputs.skip_symfony }} run: | git clone https://github.com/symfony/symfony.git --depth=1 cd symfony @@ -581,7 +581,7 @@ jobs: done exit $X - name: Test PHPUnit - if: !cancelled() + if: ${{ !cancelled() }} run: | git clone https://github.com/sebastianbergmann/phpunit.git --branch=main --depth=1 cd phpunit @@ -592,7 +592,7 @@ jobs: exit 1 fi - name: 'Symfony Preloading' - if: !cancelled() && !inputs.skip_symfony + if: ${{ !cancelled() && !inputs.skip_symfony }} run: | php /usr/bin/composer create-project symfony/symfony-demo symfony_demo --no-progress --ignore-platform-reqs cd symfony_demo @@ -600,7 +600,7 @@ jobs: sed -i 's/PHP_SAPI/"cli-server"/g' var/cache/dev/App_KernelDevDebugContainer.preload.php php -d opcache.preload=var/cache/dev/App_KernelDevDebugContainer.preload.php public/index.php - name: Test Wordpress - if: !cancelled() && !inputs.skip_wordpress + if: ${{ !cancelled() && !inputs.skip_wordpress }} run: | git clone https://github.com/WordPress/wordpress-develop.git wordpress --depth=1 cd wordpress diff --git a/NEWS b/NEWS index d080f487bb5..5c5e21e8e50 100644 --- a/NEWS +++ b/NEWS @@ -65,6 +65,10 @@ PHP NEWS the cpu mask argument with entries type different than int/string. (David Carlier) +- PDO: + . Fixed a memory leak when the GC is used to free a PDOStatment. (Girgias) + . Fixed a crash in the PDO Firebird Statement destructor. (nielsdos) + - PgSql: . Fixed build failure when the constant PGRES_TUPLES_CHUNK is not present in the system. (chschneider) diff --git a/ext/pdo/pdo_stmt.c b/ext/pdo/pdo_stmt.c index 777cb8fce3b..965311be7bc 100644 --- a/ext/pdo/pdo_stmt.c +++ b/ext/pdo/pdo_stmt.c @@ -2074,8 +2074,11 @@ out: static HashTable *dbstmt_get_gc(zend_object *object, zval **gc_data, int *gc_count) { pdo_stmt_t *stmt = php_pdo_stmt_fetch_object(object); - *gc_data = &stmt->fetch.into; - *gc_count = 1; + + zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create(); + zend_get_gc_buffer_add_zval(gc_buffer, &stmt->database_object_handle); + zend_get_gc_buffer_add_zval(gc_buffer, &stmt->fetch.into); + zend_get_gc_buffer_use(gc_buffer, gc_data, gc_count); /** * If there are no dynamic properties and the default property is 1 (that is, there is only one property diff --git a/ext/pdo/tests/pdo_stmt_cyclic_references.phpt b/ext/pdo/tests/pdo_stmt_cyclic_references.phpt new file mode 100644 index 00000000000..59d72c4b0d3 --- /dev/null +++ b/ext/pdo/tests/pdo_stmt_cyclic_references.phpt @@ -0,0 +1,138 @@ +--TEST-- +PDO Common: Cyclic PDOStatement child class +--EXTENSIONS-- +pdo +--SKIPIF-- + +--FILE-- +exec('CREATE TABLE pdo_stmt_cyclic_ref(id INT NOT NULL PRIMARY KEY, val VARCHAR(10), val2 VARCHAR(10))'); +$db->exec("INSERT INTO pdo_stmt_cyclic_ref VALUES(1, 'A', 'AA')"); +$db->exec("INSERT INTO pdo_stmt_cyclic_ref VALUES(2, 'B', 'BB')"); +$db->exec("INSERT INTO pdo_stmt_cyclic_ref VALUES(3, 'C', 'CC')"); + +$db->setAttribute(PDO::ATTR_STATEMENT_CLASS, ['CyclicStatement', [new Ref]]); + +echo "Column fetch:\n"; +$stmt = $db->query('SELECT id, val2, val FROM pdo_stmt_cyclic_ref'); +$stmt->ref->stmt = $stmt; +$stmt->setFetchMode(PDO::FETCH_COLUMN, 2); +foreach($stmt as $obj) { + var_dump($obj); +} + +echo "Class fetch:\n"; +$stmt = $db->query('SELECT id, val2, val FROM pdo_stmt_cyclic_ref'); +$stmt->ref->stmt = $stmt; +$stmt->setFetchMode(PDO::FETCH_CLASS, 'TestRow', ['Hello world']); +foreach($stmt as $obj) { + var_dump($obj); +} + +echo "Fetch into:\n"; +$stmt = $db->query('SELECT id, val2, val FROM pdo_stmt_cyclic_ref'); +$stmt->ref->stmt = $stmt; +$stmt->setFetchMode(PDO::FETCH_INTO, new TestRow('I am being fetch into')); +foreach($stmt as $obj) { + var_dump($obj); +} + +?> +--CLEAN-- + +--EXPECTF-- +Column fetch: +string(1) "A" +string(1) "B" +string(1) "C" +Class fetch: +object(TestRow)#%d (4) { + ["id"]=> + string(1) "1" + ["val"]=> + string(1) "A" + ["val2"]=> + string(2) "AA" + ["arg"]=> + string(11) "Hello world" +} +object(TestRow)#%d (4) { + ["id"]=> + string(1) "2" + ["val"]=> + string(1) "B" + ["val2"]=> + string(2) "BB" + ["arg"]=> + string(11) "Hello world" +} +object(TestRow)#%d (4) { + ["id"]=> + string(1) "3" + ["val"]=> + string(1) "C" + ["val2"]=> + string(2) "CC" + ["arg"]=> + string(11) "Hello world" +} +Fetch into: +object(TestRow)#4 (4) { + ["id"]=> + string(1) "1" + ["val"]=> + string(1) "A" + ["val2"]=> + string(2) "AA" + ["arg"]=> + string(21) "I am being fetch into" +} +object(TestRow)#4 (4) { + ["id"]=> + string(1) "2" + ["val"]=> + string(1) "B" + ["val2"]=> + string(2) "BB" + ["arg"]=> + string(21) "I am being fetch into" +} +object(TestRow)#4 (4) { + ["id"]=> + string(1) "3" + ["val"]=> + string(1) "C" + ["val2"]=> + string(2) "CC" + ["arg"]=> + string(21) "I am being fetch into" +} diff --git a/ext/pdo_firebird/firebird_statement.c b/ext/pdo_firebird/firebird_statement.c index ceb10339a51..2d91ca1f05d 100644 --- a/ext/pdo_firebird/firebird_statement.c +++ b/ext/pdo_firebird/firebird_statement.c @@ -158,8 +158,15 @@ static int pdo_firebird_stmt_dtor(pdo_stmt_t *stmt) /* {{{ */ pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; int result = 1; - /* release the statement */ - if (isc_dsql_free_statement(S->H->isc_status, &S->stmt, DSQL_drop)) { + /* TODO: for master, move this check to a separate function shared between pdo drivers. + * pdo_pgsql and pdo_mysql do this exact same thing */ + bool server_obj_usable = !Z_ISUNDEF(stmt->database_object_handle) + && IS_OBJ_VALID(EG(objects_store).object_buckets[Z_OBJ_HANDLE(stmt->database_object_handle)]) + && !(OBJ_FLAGS(Z_OBJ(stmt->database_object_handle)) & IS_OBJ_FREE_CALLED); + + /* release the statement. + * Note: if the server object is already gone then the statement was closed already as well. */ + if (server_obj_usable && isc_dsql_free_statement(S->H->isc_status, &S->stmt, DSQL_drop)) { php_firebird_error_stmt(stmt); result = 0; }