Skip to content

Commit 32402df

Browse files
authored
php_printf: introduce %pS to replace custom specifier %S (#22930)
The string formater supports custom format specifiers such as 'S' (zend_string*), but format strings using these specifiers do not pass the compiler's type checks that are performed on functions tagged with ZEND_ATTRIBUTE_FORMAT: Zend/zend_compile.c: In function 'zend_compile_closure_binding': Zend/zend_compile.c:8586:62: error: format '%S' expects argument of type 'wchar_t *', but argument 3 has type 'zend_string *' {aka 'struct _zend_string *'} [-Werror=format=] 8586 | zend_error_noreturn(E_COMPILE_ERROR, "Cannot use variable $%S twice", var_name); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~ | | | zend_string * {aka struct _zend_string *} As a result we can not use these specifiers without resorting to workarounds: * Use variants of formatting functions that do not have ZEND_ATTRIBUTE_FORMAT [1] * Or declare the format string separately [2] Here I re-introduce %S as %pS. The compiler will only see a %p specifier followed by the ordinary literal character S, so it will be happy about an argument of type zend_string*. This trick can be applied to more custom specifiers. [1] https://github.com/php/php-src/blob/0b5d9801ec3b53e84388239a5b9f85d005318b64/Zend/zend_compile.c#L8586-L8587 [2] https://github.com/php/php-src/blob/edc169e7705d5e4411865e9be92b50e82be78f4e/Zend/zend_partial.c#L680
1 parent 9add43a commit 32402df

4 files changed

Lines changed: 35 additions & 3 deletions

File tree

UPGRADING.INTERNALS

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,19 @@ PHP 8.6 INTERNALS UPGRADE NOTES
1414
1. Internal API changes
1515
========================
1616

17+
- Breaking changes:
18+
. String formatting functions now support the custom conversion specifiers
19+
'pS' (zend_string*) and 'pp' (same as 'p'). Following the 'p' specifier with
20+
an alpha-numeric character other than 'S' or 'p' is now an error.
21+
22+
Examples:
23+
24+
zend_string *str;
25+
zend_spprintf("%pS", str); // valid, same as "%S"
26+
zend_spprintf("%pp", str); // valid, same as "%p"
27+
zend_spprintf("%pA", str); // invalid
28+
zend_spprintf("%ppA", str); // valid, same as zend_spprintf("%p%c", str, 'A')
29+
1730
- Removed:
1831
. The misnamed ZVAL_IS_NULL() has been removed. Use Z_ISNULL() instead.
1932
. The zval_is_true() alias of zend_is_true() has been removed. Call

Zend/zend_compile.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8580,8 +8580,8 @@ static void zend_compile_closure_binding(znode *closure, zend_op_array *op_array
85808580

85818581
value = zend_hash_add(op_array->static_variables, var_name, &EG(uninitialized_zval));
85828582
if (!value) {
8583-
zend_error_noreturn_unchecked(E_COMPILE_ERROR,
8584-
"Cannot use variable $%S twice", var_name);
8583+
zend_error_noreturn(E_COMPILE_ERROR,
8584+
"Cannot use variable $%pS twice", var_name);
85858585
}
85868586

85878587
CG(zend_lineno) = zend_ast_get_lineno(var_name_ast);

ext/sqlite3/sqlite3.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1660,7 +1660,7 @@ static int php_sqlite3_bind_params(php_sqlite3_stmt *stmt_obj) /* {{{ */
16601660
break;
16611661

16621662
default:
1663-
php_sqlite3_error(stmt_obj->db_obj, 0, "Unknown parameter type: %pd for parameter %pd", param->type, param->param_number);
1663+
php_sqlite3_error(stmt_obj->db_obj, 0, "Unknown parameter type: " ZEND_LONG_FMT " for parameter " ZEND_LONG_FMT, param->type, param->param_number);
16641664
return FAILURE;
16651665
}
16661666
} ZEND_HASH_FOREACH_END();

main/spprintf.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ static void xbuf_format_converter(void *xbuf, bool is_char, const char *fmt, va_
362362
break;
363363
}
364364
case 'S': {
365+
format_zend_string:;
365366
zend_string *str = va_arg(ap, zend_string*);
366367
s_len = ZSTR_LEN(str);
367368
s = ZSTR_VAL(str);
@@ -665,6 +666,24 @@ static void xbuf_format_converter(void *xbuf, bool is_char, const char *fmt, va_
665666
* we print "%p" to indicate that we don't handle "%p".
666667
*/
667668
case 'p':
669+
/* %p[alnum]+ extensions */
670+
switch (*(fmt+1)) {
671+
case 'S':
672+
/* zend_string* */
673+
fmt++;
674+
goto format_zend_string;
675+
case 'p':
676+
/* pointer */
677+
fmt++;
678+
break;
679+
default:
680+
if (isalnum(*(fmt+1))) {
681+
zend_error_noreturn(E_CORE_ERROR,
682+
"Invalid printf specifier \"p%c\"", *(fmt+1));
683+
}
684+
break;
685+
}
686+
/* Normal %p */
668687
if (sizeof(char *) <= sizeof(uint64_t)) {
669688
ui_num = (uint64_t)((size_t) va_arg(ap, char *));
670689
s = ap_php_conv_p2(ui_num, 4, 'x',

0 commit comments

Comments
 (0)