Zend: Add ZPP F type check for callables that do not free trampolines

As refetching it with the new FCC API does get tedious
This commit is contained in:
George Peter Banyard 2023-10-06 18:07:37 +01:00
parent d24f07cbfe
commit d86314939c
4 changed files with 39 additions and 17 deletions

View file

@ -19,6 +19,11 @@ PHP 8.4 INTERNALS UPGRADE NOTES
to do this at the call site anymore. Writing the handle should happen after to do this at the call site anymore. Writing the handle should happen after
successful registration. successful registration.
* ZPP now accepts a F or Z_PARAM_FUNC_NO_TRAMPOLINE_FREE type check.
This is identical to the 'f' or Z_PARAM_FUNC type check, except the FCC is
always initialized because it doesn't free trampolines.
Trampolines MUST be freed using zend_release_fcall_info_cache() or consumed.
======================== ========================
2. Build system changes 2. Build system changes
======================== ========================

View file

@ -981,6 +981,7 @@ static const char *zend_parse_arg_impl(zval *arg, va_list *va, const char **spec
} }
break; break;
case 'F':
case 'f': case 'f':
{ {
zend_fcall_info *fci = va_arg(*va, zend_fcall_info *); zend_fcall_info *fci = va_arg(*va, zend_fcall_info *);
@ -995,10 +996,12 @@ static const char *zend_parse_arg_impl(zval *arg, va_list *va, const char **spec
if (zend_fcall_info_init(arg, 0, fci, fcc, NULL, &is_callable_error) == SUCCESS) { if (zend_fcall_info_init(arg, 0, fci, fcc, NULL, &is_callable_error) == SUCCESS) {
ZEND_ASSERT(!is_callable_error); ZEND_ASSERT(!is_callable_error);
if (c == 'f') {
/* Release call trampolines: The function may not get called, in which case /* Release call trampolines: The function may not get called, in which case
* the trampoline will leak. Force it to be refetched during * the trampoline will leak. Force it to be refetched during
* zend_call_function instead. */ * zend_call_function instead. */
zend_release_fcall_info_cache(fcc); zend_release_fcall_info_cache(fcc);
}
break; break;
} }
@ -1109,7 +1112,7 @@ static zend_result zend_parse_va_args(uint32_t num_args, const char *type_spec,
case 'o': case 'O': case 'o': case 'O':
case 'z': case 'Z': case 'z': case 'Z':
case 'C': case 'h': case 'C': case 'h':
case 'f': case 'A': case 'f': case 'F': case 'A':
case 'H': case 'p': case 'H': case 'p':
case 'S': case 'P': case 'S': case 'P':
case 'L': case 'n': case 'L': case 'n':

View file

@ -1794,9 +1794,9 @@ ZEND_API ZEND_COLD void zend_argument_value_error(uint32_t arg_num, const char *
Z_PARAM_DOUBLE_EX(dest, is_null, 1, 0) Z_PARAM_DOUBLE_EX(dest, is_null, 1, 0)
/* old "f" */ /* old "f" */
#define Z_PARAM_FUNC_EX(dest_fci, dest_fcc, check_null, deref) \ #define Z_PARAM_FUNC_EX(dest_fci, dest_fcc, check_null, deref, free_trampoline) \
Z_PARAM_PROLOGUE(deref, 0); \ Z_PARAM_PROLOGUE(deref, 0); \
if (UNEXPECTED(!zend_parse_arg_func(_arg, &dest_fci, &dest_fcc, check_null, &_error))) { \ if (UNEXPECTED(!zend_parse_arg_func(_arg, &dest_fci, &dest_fcc, check_null, &_error, free_trampoline))) { \
if (!_error) { \ if (!_error) { \
_expected_type = check_null ? Z_EXPECTED_FUNC_OR_NULL : Z_EXPECTED_FUNC; \ _expected_type = check_null ? Z_EXPECTED_FUNC_OR_NULL : Z_EXPECTED_FUNC; \
_error_code = ZPP_ERROR_WRONG_ARG; \ _error_code = ZPP_ERROR_WRONG_ARG; \
@ -1807,13 +1807,19 @@ ZEND_API ZEND_COLD void zend_argument_value_error(uint32_t arg_num, const char *
} \ } \
#define Z_PARAM_FUNC(dest_fci, dest_fcc) \ #define Z_PARAM_FUNC(dest_fci, dest_fcc) \
Z_PARAM_FUNC_EX(dest_fci, dest_fcc, 0, 0) Z_PARAM_FUNC_EX(dest_fci, dest_fcc, 0, 0, true)
#define Z_PARAM_FUNC_NO_TRAMPOLINE_FREE(dest_fci, dest_fcc) \
Z_PARAM_FUNC_EX(dest_fci, dest_fcc, 0, 0, false)
#define Z_PARAM_FUNC_OR_NULL(dest_fci, dest_fcc) \ #define Z_PARAM_FUNC_OR_NULL(dest_fci, dest_fcc) \
Z_PARAM_FUNC_EX(dest_fci, dest_fcc, 1, 0) Z_PARAM_FUNC_EX(dest_fci, dest_fcc, 1, 0, true)
#define Z_PARAM_FUNC_NO_TRAMPOLINE_FREE_OR_NULL(dest_fci, dest_fcc) \
Z_PARAM_FUNC_EX(dest_fci, dest_fcc, 1, 0, false)
#define Z_PARAM_FUNC_OR_NULL_WITH_ZVAL(dest_fci, dest_fcc, dest_zp) \ #define Z_PARAM_FUNC_OR_NULL_WITH_ZVAL(dest_fci, dest_fcc, dest_zp) \
Z_PARAM_FUNC_EX(dest_fci, dest_fcc, 1, 0) \ Z_PARAM_FUNC_EX(dest_fci, dest_fcc, 1, 0, true) \
Z_PARAM_GET_PREV_ZVAL(dest_zp) Z_PARAM_GET_PREV_ZVAL(dest_zp)
/* old "h" */ /* old "h" */
@ -2428,7 +2434,7 @@ static zend_always_inline bool zend_parse_arg_resource(zval *arg, zval **dest, b
return 1; return 1;
} }
static zend_always_inline bool zend_parse_arg_func(zval *arg, zend_fcall_info *dest_fci, zend_fcall_info_cache *dest_fcc, bool check_null, char **error) static zend_always_inline bool zend_parse_arg_func(zval *arg, zend_fcall_info *dest_fci, zend_fcall_info_cache *dest_fcc, bool check_null, char **error, bool free_trampoline)
{ {
if (check_null && UNEXPECTED(Z_TYPE_P(arg) == IS_NULL)) { if (check_null && UNEXPECTED(Z_TYPE_P(arg) == IS_NULL)) {
dest_fci->size = 0; dest_fci->size = 0;
@ -2437,10 +2443,12 @@ static zend_always_inline bool zend_parse_arg_func(zval *arg, zend_fcall_info *d
} else if (UNEXPECTED(zend_fcall_info_init(arg, 0, dest_fci, dest_fcc, NULL, error) != SUCCESS)) { } else if (UNEXPECTED(zend_fcall_info_init(arg, 0, dest_fci, dest_fcc, NULL, error) != SUCCESS)) {
return 0; return 0;
} }
if (free_trampoline) {
/* Release call trampolines: The function may not get called, in which case /* Release call trampolines: The function may not get called, in which case
* the trampoline will leak. Force it to be refetched during * the trampoline will leak. Force it to be refetched during
* zend_call_function instead. */ * zend_call_function instead. */
zend_release_fcall_info_cache(dest_fcc); zend_release_fcall_info_cache(dest_fcc);
}
return 1; return 1;
} }

View file

@ -70,8 +70,14 @@ A - array or object (zval*)
b - boolean (bool) b - boolean (bool)
C - class (zend_class_entry*) C - class (zend_class_entry*)
d - double (double) d - double (double)
f - function or array containing php method call info (returned as f - PHP callable containing php function/method call info (returned as
zend_fcall_info and zend_fcall_info_cache) zend_fcall_info and zend_fcall_info_cache).
The FCC may be uninitialized if the callable is a trampoline.
F - PHP callable containing php function/method call info (returned as
zend_fcall_info and zend_fcall_info_cache).
The FCC will *always* be initialized, even if the callable is a trampoline.
A trampoline *must* be consumed or released with
zend_release_fcall_info_cache().
h - array (returned as HashTable*) h - array (returned as HashTable*)
H - array or HASH_OF(object) (returned as HashTable*) H - array or HASH_OF(object) (returned as HashTable*)
l - long (zend_long) l - long (zend_long)