Skip to content

Commit 2c5dc5d

Browse files
committed
Add ZEND_ACC2_FORBID_DYN_CALLS
Functions that use zend_forbid_dynamic_call() must be flagged with ZEND_ACC2_FORBID_DYN_CALLS. In stubs, this is done by using @forbid-dynamic-calls. To ensure consistency, we assert that the flag exists in zend_forbid_dynamic_call(), and we assert that flagged functions thrown after a dynamic call. Closes GH-21818
1 parent da58c65 commit 2c5dc5d

18 files changed

+139
-21
lines changed

UPGRADING.INTERNALS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ PHP 8.6 INTERNALS UPGRADE NOTES
8888
ZEND_AST_TRAIT_METHOD_REFERENCE.
8989
. The EMPTY_SWITCH_DEFAULT_CASE() macro has been removed. Use
9090
default: ZEND_UNREACHABLE(); instead.
91+
. Functions using zend_forbid_dynamic_call() *must* be flagged with
92+
ZEND_ACC2_FORBID_DYN_CALLS (@forbid-dynamic-calls in stubs). In debug
93+
builds, failing to include that flag will lead to assertion failures.
9194

9295
========================
9396
2. Build system changes

Zend/zend_API.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,8 @@ static zend_always_inline zend_result zend_forbid_dynamic_call(void)
896896
const zend_execute_data *ex = EG(current_execute_data);
897897
ZEND_ASSERT(ex != NULL && ex->func != NULL);
898898

899+
ZEND_ASSERT(ex->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS);
900+
899901
if (ZEND_CALL_INFO(ex) & ZEND_CALL_DYNAMIC) {
900902
zend_string *function_or_method_name = get_active_function_or_method_name();
901903
zend_throw_error(NULL, "Cannot call %.*s() dynamically",

Zend/zend_builtin_functions.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
+----------------------------------------------------------------------+
1717
*/
1818

19+
#include "php_version.h"
1920
#include "zend.h"
2021
#include "zend_API.h"
2122
#include "zend_attributes.h"

Zend/zend_builtin_functions.stub.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,16 @@ function die(string|int $status = 0): never {}
1818
/** @refcount 1 */
1919
function zend_version(): string {}
2020

21+
/** @forbid-dynamic-calls */
2122
function func_num_args(): int {}
2223

24+
/** @forbid-dynamic-calls */
2325
function func_get_arg(int $position): mixed {}
2426

25-
/** @return array<int, mixed> */
27+
/**
28+
* @return array<int, mixed>
29+
* @forbid-dynamic-calls
30+
*/
2631
function func_get_args(): array {}
2732

2833
function strlen(string $string): int {}
@@ -156,6 +161,7 @@ function get_defined_functions(bool $exclude_disabled = true): array {}
156161
/**
157162
* @return array<string, mixed|ref>
158163
* @refcount 1
164+
* @forbid-dynamic-calls
159165
*/
160166
function get_defined_vars(): array {}
161167

Zend/zend_builtin_functions_arginfo.h

Lines changed: 21 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Zend/zend_closures.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,7 @@ static ZEND_NAMED_FUNCTION(zend_closure_internal_handler) /* {{{ */
749749
{
750750
zend_closure *closure = (zend_closure*)ZEND_CLOSURE_OBJECT(EX(func));
751751
closure->orig_internal_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU);
752+
ZEND_ASSERT(!(closure->func.common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS) || EG(exception));
752753
// Assign to EX(this) so that it is released after observer checks etc.
753754
ZEND_ADD_CALL_FLAG(execute_data, ZEND_CALL_RELEASE_THIS);
754755
Z_OBJ(EX(This)) = &closure->std;

Zend/zend_compile.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -412,10 +412,11 @@ typedef struct _zend_oparray_context {
412412
/* op_array uses strict mode types | | | */
413413
#define ZEND_ACC_STRICT_TYPES (1U << 31) /* | X | | */
414414
/* | | | */
415-
/* Function Flags 2 (fn_flags2) (unused: 0-31) | | | */
415+
/* Function Flags 2 (fn_flags2) (unused: 1-31) | | | */
416416
/* ============================ | | | */
417417
/* | | | */
418-
/* #define ZEND_ACC2_EXAMPLE (1 << 0) | X | | */
418+
/* Function forbids dynamic calls | | | */
419+
#define ZEND_ACC2_FORBID_DYN_CALLS (1 << 0) /* | X | | */
419420

420421
#define ZEND_ACC_PPP_MASK (ZEND_ACC_PUBLIC | ZEND_ACC_PROTECTED | ZEND_ACC_PRIVATE)
421422
#define ZEND_ACC_PPP_SET_MASK (ZEND_ACC_PUBLIC_SET | ZEND_ACC_PROTECTED_SET | ZEND_ACC_PRIVATE_SET)

Zend/zend_execute_API.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,6 +1030,7 @@ zend_result zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_
10301030
}
10311031
ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE)
10321032
? Z_ISREF_P(fci->retval) : !Z_ISREF_P(fci->retval));
1033+
ZEND_ASSERT(!(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS));
10331034
}
10341035
#endif
10351036
ZEND_OBSERVER_FCALL_END(call, fci->retval);

Zend/zend_vm_def.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4160,6 +4160,8 @@ ZEND_VM_HOT_HANDLER(129, ZEND_DO_ICALL, ANY, ANY, SPEC(RETVAL,OBSERVER))
41604160
ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE)
41614161
? Z_ISREF_P(ret) : !Z_ISREF_P(ret));
41624162
zend_verify_internal_func_info(call->func, ret);
4163+
ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC)
4164+
|| !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS));
41634165
}
41644166
#endif
41654167
ZEND_OBSERVER_FCALL_END(call, EG(exception) ? NULL : ret);
@@ -4291,6 +4293,8 @@ ZEND_VM_HOT_HANDLER(131, ZEND_DO_FCALL_BY_NAME, ANY, ANY, SPEC(RETVAL,OBSERVER))
42914293
ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE)
42924294
? Z_ISREF_P(ret) : !Z_ISREF_P(ret));
42934295
zend_verify_internal_func_info(call->func, ret);
4296+
ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC)
4297+
|| !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS));
42944298
}
42954299
ZEND_ASSERT(opline->result_type != IS_TMP_VAR || !Z_ISREF_P(ret));
42964300
#endif
@@ -4422,6 +4426,8 @@ ZEND_VM_HOT_HANDLER(60, ZEND_DO_FCALL, ANY, ANY, SPEC(RETVAL,OBSERVER))
44224426
ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE)
44234427
? Z_ISREF_P(ret) : !Z_ISREF_P(ret));
44244428
zend_verify_internal_func_info(call->func, ret);
4429+
ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC)
4430+
|| !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS));
44254431
}
44264432
ZEND_ASSERT(opline->result_type != IS_TMP_VAR || !Z_ISREF_P(ret));
44274433
#endif
@@ -9131,6 +9137,8 @@ ZEND_VM_HANDLER(158, ZEND_CALL_TRAMPOLINE, ANY, ANY, SPEC(OBSERVER))
91319137
ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE)
91329138
? Z_ISREF_P(ret) : !Z_ISREF_P(ret));
91339139
zend_verify_internal_func_info(call->func, ret);
9140+
ZEND_ASSERT(!(ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC)
9141+
|| !(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS));
91349142
}
91359143
#endif
91369144
ZEND_OBSERVER_FCALL_END(call, EG(exception) ? NULL : ret);

Zend/zend_vm_execute.h

Lines changed: 44 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)