Skip to content

Commit 764c499

Browse files
committed
ext/sqlite3: Use new F ZPP modifier
1 parent f68d725 commit 764c499

5 files changed

+248
-43
lines changed

ext/sqlite3/sqlite3.c

Lines changed: 38 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ static int php_sqlite3_compare_stmt_zval_free(php_sqlite3_free_list **free_list,
4444
RETURN_THROWS(); \
4545
}
4646

47+
#define SQLITE3_CHECK_INITIALIZED_FREE_TRAMPOLINE(db_obj, member, class_name, trampoline_fcc) \
48+
if (!(db_obj) || !(member)) { \
49+
zend_release_fcall_info_cache((trampoline_fcc)); \
50+
zend_throw_error(NULL, "The " #class_name " object has not been correctly initialised or is already closed"); \
51+
RETURN_THROWS(); \
52+
}
53+
4754
#define SQLITE3_CHECK_INITIALIZED_STMT(member, class_name) \
4855
if (!(member)) { \
4956
zend_throw_error(NULL, "The " #class_name " object has not been correctly initialised or is already closed"); \
@@ -942,27 +949,23 @@ PHP_METHOD(SQLite3, createFunction)
942949
zend_long flags = 0;
943950
db_obj = Z_SQLITE3_DB_P(object);
944951

945-
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sf|ll", &sql_func, &sql_func_len, &fci, &fcc, &sql_func_num_args, &flags) == FAILURE) {
952+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sF|ll", &sql_func, &sql_func_len, &fci, &fcc, &sql_func_num_args, &flags) == FAILURE) {
953+
zend_release_fcall_info_cache(&fcc);
946954
RETURN_THROWS();
947955
}
948956

949-
SQLITE3_CHECK_INITIALIZED(db_obj, db_obj->initialised, SQLite3)
957+
SQLITE3_CHECK_INITIALIZED_FREE_TRAMPOLINE(db_obj, db_obj->initialised, SQLite3, &fcc);
950958

951959
if (!sql_func_len) {
960+
/* TODO Add warning/ValueError that name cannot be empty? */
961+
zend_release_fcall_info_cache(&fcc);
952962
RETURN_FALSE;
953963
}
954964

955965
func = (php_sqlite3_func *)ecalloc(1, sizeof(*func));
956966

957967
if (sqlite3_create_function(db_obj->db, sql_func, sql_func_num_args, flags | SQLITE_UTF8, func, php_sqlite3_callback_func, NULL, NULL) == SQLITE_OK) {
958968
func->func_name = estrdup(sql_func);
959-
960-
if (!ZEND_FCC_INITIALIZED(fcc)) {
961-
zend_is_callable_ex(&fci.function_name, NULL, IS_CALLABLE_SUPPRESS_DEPRECATIONS, NULL, &fcc, NULL);
962-
/* Call trampoline has been cleared by zpp. Refetch it, because we want to deal
963-
* with it outselves. It is important that it is not refetched on every call,
964-
* because calls may occur from different scopes. */
965-
}
966969
zend_fcc_dup(&func->func, &fcc);
967970

968971
func->argc = sql_func_num_args;
@@ -972,6 +975,7 @@ PHP_METHOD(SQLite3, createFunction)
972975
RETURN_TRUE;
973976
}
974977
efree(func);
978+
zend_release_fcall_info_cache(&fcc);
975979

976980
RETURN_FALSE;
977981
}
@@ -990,34 +994,27 @@ PHP_METHOD(SQLite3, createAggregate)
990994
zend_long sql_func_num_args = -1;
991995
db_obj = Z_SQLITE3_DB_P(object);
992996

993-
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sff|l", &sql_func, &sql_func_len, &step_fci, &step_fcc, &fini_fci, &fini_fcc, &sql_func_num_args) == FAILURE) {
994-
RETURN_THROWS();
997+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sFF|l", &sql_func, &sql_func_len, &step_fci, &step_fcc, &fini_fci, &fini_fcc, &sql_func_num_args) == FAILURE) {
998+
goto error;
995999
}
9961000

997-
SQLITE3_CHECK_INITIALIZED(db_obj, db_obj->initialised, SQLite3)
1001+
/* Cannot use SQLITE3_CHECK_INITIALIZED_FREE_TRAMPOLINE() as we have 2 FCCs */
1002+
if (!db_obj || !db_obj->initialised) {
1003+
zend_throw_error(NULL, "The SQLite3 object has not been correctly initialised or is already closed");
1004+
goto error;
1005+
}
9981006

9991007
if (!sql_func_len) {
1000-
RETURN_FALSE;
1008+
/* TODO Add warning/ValueError that name cannot be empty? */
1009+
goto error;
10011010
}
10021011

10031012
func = (php_sqlite3_func *)ecalloc(1, sizeof(*func));
10041013

10051014
if (sqlite3_create_function(db_obj->db, sql_func, sql_func_num_args, SQLITE_UTF8, func, NULL, php_sqlite3_callback_step, php_sqlite3_callback_final) == SQLITE_OK) {
10061015
func->func_name = estrdup(sql_func);
10071016

1008-
if (!ZEND_FCC_INITIALIZED(step_fcc)) {
1009-
/* Call trampoline has been cleared by zpp. Refetch it, because we want to deal
1010-
* with it outselves. It is important that it is not refetched on every call,
1011-
* because calls may occur from different scopes. */
1012-
zend_is_callable_ex(&step_fci.function_name, NULL, IS_CALLABLE_SUPPRESS_DEPRECATIONS, NULL, &step_fcc, NULL);
1013-
}
10141017
zend_fcc_dup(&func->step, &step_fcc);
1015-
if (!ZEND_FCC_INITIALIZED(fini_fcc)) {
1016-
/* Call trampoline has been cleared by zpp. Refetch it, because we want to deal
1017-
* with it outselves. It is important that it is not refetched on every call,
1018-
* because calls may occur from different scopes. */
1019-
zend_is_callable_ex(&fini_fci.function_name, NULL, IS_CALLABLE_SUPPRESS_DEPRECATIONS, NULL, &fini_fcc, NULL);
1020-
}
10211018
zend_fcc_dup(&func->fini, &fini_fcc);
10221019

10231020
func->argc = sql_func_num_args;
@@ -1028,6 +1025,14 @@ PHP_METHOD(SQLite3, createAggregate)
10281025
}
10291026
efree(func);
10301027

1028+
error:
1029+
if (ZEND_FCC_INITIALIZED(step_fcc)) {
1030+
zend_release_fcall_info_cache(&step_fcc);
1031+
}
1032+
if (ZEND_FCC_INITIALIZED(fini_fcc)) {
1033+
zend_release_fcall_info_cache(&fini_fcc);
1034+
}
1035+
10311036
RETURN_FALSE;
10321037
}
10331038
/* }}} */
@@ -1044,26 +1049,22 @@ PHP_METHOD(SQLite3, createCollation)
10441049
zend_fcall_info_cache fcc;
10451050
db_obj = Z_SQLITE3_DB_P(object);
10461051

1047-
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sf", &collation_name, &collation_name_len, &fci, &fcc) == FAILURE) {
1052+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sF", &collation_name, &collation_name_len, &fci, &fcc) == FAILURE) {
10481053
RETURN_THROWS();
10491054
}
10501055

1051-
SQLITE3_CHECK_INITIALIZED(db_obj, db_obj->initialised, SQLite3)
1056+
SQLITE3_CHECK_INITIALIZED_FREE_TRAMPOLINE(db_obj, db_obj->initialised, SQLite3, &fcc);
10521057

10531058
if (!collation_name_len) {
1059+
/* TODO Add warning/ValueError that name cannot be empty? */
1060+
zend_release_fcall_info_cache(&fcc);
10541061
RETURN_FALSE;
10551062
}
10561063

10571064
collation = (php_sqlite3_collation *)ecalloc(1, sizeof(*collation));
10581065
if (sqlite3_create_collation(db_obj->db, collation_name, SQLITE_UTF8, collation, php_sqlite3_callback_compare) == SQLITE_OK) {
10591066
collation->collation_name = estrdup(collation_name);
10601067

1061-
if (!ZEND_FCC_INITIALIZED(fcc)) {
1062-
/* Call trampoline has been cleared by zpp. Refetch it, because we want to deal
1063-
* with it outselves. It is important that it is not refetched on every call,
1064-
* because calls may occur from different scopes. */
1065-
zend_is_callable_ex(&fci.function_name, NULL, IS_CALLABLE_SUPPRESS_DEPRECATIONS, NULL, &fcc, NULL);
1066-
}
10671068
zend_fcc_dup(&collation->cmp_func, &fcc);
10681069

10691070
collation->next = db_obj->collations;
@@ -1072,6 +1073,7 @@ PHP_METHOD(SQLite3, createCollation)
10721073
RETURN_TRUE;
10731074
}
10741075
efree(collation);
1076+
zend_release_fcall_info_cache(&fcc);
10751077

10761078
RETURN_FALSE;
10771079
}
@@ -1317,10 +1319,10 @@ PHP_METHOD(SQLite3, setAuthorizer)
13171319
zend_fcall_info_cache fcc;
13181320

13191321
ZEND_PARSE_PARAMETERS_START(1, 1)
1320-
Z_PARAM_FUNC_OR_NULL(fci, fcc)
1322+
Z_PARAM_FUNC_NO_TRAMPOLINE_FREE_OR_NULL(fci, fcc)
13211323
ZEND_PARSE_PARAMETERS_END();
13221324

1323-
SQLITE3_CHECK_INITIALIZED(db_obj, db_obj->initialised, SQLite3)
1325+
SQLITE3_CHECK_INITIALIZED_FREE_TRAMPOLINE(db_obj, db_obj->initialised, SQLite3, &fcc);
13241326

13251327
/* Clear previously set callback */
13261328
if (ZEND_FCC_INITIALIZED(db_obj->authorizer_fcc)) {
@@ -1329,14 +1331,7 @@ PHP_METHOD(SQLite3, setAuthorizer)
13291331

13301332
/* Only enable userland authorizer if argument is not NULL */
13311333
if (ZEND_FCI_INITIALIZED(fci)) {
1332-
if (!ZEND_FCC_INITIALIZED(fcc)) {
1333-
zend_is_callable_ex(&fci.function_name, NULL, IS_CALLABLE_SUPPRESS_DEPRECATIONS, NULL, &fcc, NULL);
1334-
/* Call trampoline has been cleared by zpp. Refetch it, because we want to deal
1335-
* with it outselves. It is important that it is not refetched on every call,
1336-
* because calls may occur from different scopes. */
1337-
}
1338-
db_obj->authorizer_fcc = fcc;
1339-
zend_fcc_addref(&db_obj->authorizer_fcc);
1334+
zend_fcc_dup(&db_obj->authorizer_fcc, &fcc);
13401335
}
13411336

13421337
RETURN_TRUE;
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
--TEST--
2+
SQLite3::createAggregate() use F ZPP for trampoline callback and does not leak
3+
--EXTENSIONS--
4+
sqlite3
5+
--FILE--
6+
<?php
7+
8+
require_once(__DIR__ . '/new_db.inc');
9+
10+
class TrampolineTest {
11+
public function __call(string $name, array $arguments) {
12+
echo 'Trampoline for ', $name, PHP_EOL;
13+
$context = $arguments[0];
14+
if ($name === 'finalize') {
15+
return implode(',', $context['values']);
16+
}
17+
if (empty($context)) {
18+
$context = ['total' => 0, 'values' => []];
19+
}
20+
$context['total'] += (int) $arguments[2];
21+
$context['values'][] = $context['total'];
22+
return $context;
23+
}
24+
}
25+
$o = new TrampolineTest();
26+
$step = [$o, 'step'];
27+
$finalize = [$o, 'finalize'];
28+
29+
var_dump($db->createAggregate('', $step, $finalize, 1));
30+
31+
try {
32+
var_dump($db->createAggregate('S', $step, $finalize, new stdClass()));
33+
} catch (\Throwable $e) {
34+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
35+
}
36+
try {
37+
var_dump($db->createAggregate('S', $step, 'no_func', 1));
38+
} catch (\Throwable $e) {
39+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
40+
}
41+
try {
42+
var_dump($db->createAggregate('S', 'no_func', $finalize, 1));
43+
} catch (\Throwable $e) {
44+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
45+
}
46+
47+
try {
48+
var_dump($db->createAggregate('S', $step, $finalize, 'not a number'));
49+
} catch (\Throwable $e) {
50+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
51+
}
52+
53+
echo "Invalid SQLite3 object:\n";
54+
$rc = new ReflectionClass(SQLite3::class);
55+
$obj = $rc->newInstanceWithoutConstructor();
56+
57+
try {
58+
var_dump($obj->createAggregate('S', $step, $finalize, 1));
59+
} catch (\Throwable $e) {
60+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
61+
}
62+
63+
var_dump($db->createAggregate('S', $step, $finalize, 1));
64+
65+
echo "Closing database\n";
66+
var_dump($db->close());
67+
echo "Done\n";
68+
?>
69+
--EXPECT--
70+
bool(false)
71+
TypeError: SQLite3::createAggregate(): Argument #4 ($argCount) must be of type int, stdClass given
72+
TypeError: SQLite3::createAggregate(): Argument #3 ($finalCallback) must be a valid callback, function "no_func" not found or invalid function name
73+
TypeError: SQLite3::createAggregate(): Argument #2 ($stepCallback) must be a valid callback, function "no_func" not found or invalid function name
74+
TypeError: SQLite3::createAggregate(): Argument #4 ($argCount) must be of type int, string given
75+
Invalid SQLite3 object:
76+
Error: The SQLite3 object has not been correctly initialised or is already closed
77+
bool(true)
78+
Closing database
79+
bool(true)
80+
Done
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
--TEST--
2+
SQLite3::createFunction use F ZPP for trampoline callback and does not leak
3+
--EXTENSIONS--
4+
sqlite3
5+
--FILE--
6+
<?php
7+
8+
require_once(__DIR__ . '/new_db.inc');
9+
10+
class TrampolineTest {
11+
public function __call(string $name, array $arguments) {
12+
echo 'Trampoline for ', $name, PHP_EOL;
13+
return strnatcmp(...$arguments);
14+
}
15+
}
16+
$o = new TrampolineTest();
17+
$callback = [$o, 'NAT'];
18+
19+
var_dump($db->createCollation('', $callback));
20+
try {
21+
var_dump($db->createCollation('NAT', $callback, new stdClass()));
22+
} catch (\Throwable $e) {
23+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
24+
}
25+
26+
echo "Invalid SQLite3 object:\n";
27+
$rc = new ReflectionClass(SQLite3::class);
28+
$obj = $rc->newInstanceWithoutConstructor();
29+
30+
try {
31+
var_dump($obj->createCollation('NAT', $callback));
32+
} catch (\Throwable $e) {
33+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
34+
}
35+
36+
var_dump($db->createCollation('NAT', $callback));
37+
38+
?>
39+
--EXPECT--
40+
bool(false)
41+
ArgumentCountError: SQLite3::createCollation() expects exactly 2 arguments, 3 given
42+
Invalid SQLite3 object:
43+
Error: The SQLite3 object has not been correctly initialised or is already closed
44+
bool(true)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
--TEST--
2+
SQLite3::createFunction use F ZPP for trampoline callback and does not leak
3+
--EXTENSIONS--
4+
sqlite3
5+
--FILE--
6+
<?php
7+
8+
require_once(__DIR__ . '/new_db.inc');
9+
10+
class TrampolineTest {
11+
public function __call(string $name, array $arguments) {
12+
echo 'Trampoline for ', $name, PHP_EOL;
13+
return strtoupper($arguments[0]);
14+
}
15+
}
16+
$o = new TrampolineTest();
17+
$callback = [$o, 'strtoupper'];
18+
19+
var_dump($db->createfunction('', $callback));
20+
21+
try {
22+
var_dump($db->createfunction('strtoupper', $callback, new stdClass()));
23+
} catch (\Throwable $e) {
24+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
25+
}
26+
27+
echo "Invalid SQLite3 object:\n";
28+
$rc = new ReflectionClass(SQLite3::class);
29+
$obj = $rc->newInstanceWithoutConstructor();
30+
31+
try {
32+
var_dump($obj->createfunction('strtoupper', $callback));
33+
} catch (\Throwable $e) {
34+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
35+
}
36+
37+
var_dump($db->createfunction('strtoupper', $callback));
38+
39+
?>
40+
--EXPECT--
41+
bool(false)
42+
TypeError: SQLite3::createFunction(): Argument #3 ($argCount) must be of type int, stdClass given
43+
Invalid SQLite3 object:
44+
Error: The SQLite3 object has not been correctly initialised or is already closed
45+
bool(true)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
SQLite3::setAuthorizer use F ZPP for trampoline callback and does not leak
3+
--EXTENSIONS--
4+
sqlite3
5+
--FILE--
6+
<?php
7+
8+
class TrampolineTest {
9+
public function __call(string $name, array $arguments) {
10+
echo 'Trampoline for ', $name, PHP_EOL;
11+
if ($arguments[0] == SQLite3::SELECT) {
12+
return SQLite3::OK;
13+
}
14+
15+
return SQLite3::DENY;
16+
}
17+
}
18+
$o = new TrampolineTest();
19+
$callback = [$o, 'authorizer'];
20+
21+
$db = new SQLite3(':memory:');
22+
$db->enableExceptions(true);
23+
24+
echo "Invalid SQLite3 object:\n";
25+
$rc = new ReflectionClass(SQLite3::class);
26+
$obj = $rc->newInstanceWithoutConstructor();
27+
28+
try {
29+
var_dump($obj->setAuthorizer($callback));
30+
} catch (\Throwable $e) {
31+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
32+
}
33+
34+
$db->setAuthorizer($callback);
35+
36+
?>
37+
DONE
38+
--EXPECT--
39+
Invalid SQLite3 object:
40+
Error: The SQLite3 object has not been correctly initialised or is already closed
41+
DONE

0 commit comments

Comments
 (0)