Skip to content

Commit 8b437c7

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.
1 parent 9a34fa5 commit 8b437c7

16 files changed

Lines changed: 98 additions & 21 deletions

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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,9 @@ 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+
#if ZEND_DEBUG
753+
ZEND_ASSERT(!(closure->func.common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS) || EG(exception));
754+
#endif
752755
// Assign to EX(this) so that it is released after observer checks etc.
753756
ZEND_ADD_CALL_FLAG(execute_data, ZEND_CALL_RELEASE_THIS);
754757
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: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4160,6 +4160,9 @@ 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+
if (ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) {
4164+
ZEND_ASSERT(!(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS));
4165+
}
41634166
}
41644167
#endif
41654168
ZEND_OBSERVER_FCALL_END(call, EG(exception) ? NULL : ret);
@@ -4291,6 +4294,9 @@ ZEND_VM_HOT_HANDLER(131, ZEND_DO_FCALL_BY_NAME, ANY, ANY, SPEC(RETVAL,OBSERVER))
42914294
ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE)
42924295
? Z_ISREF_P(ret) : !Z_ISREF_P(ret));
42934296
zend_verify_internal_func_info(call->func, ret);
4297+
if (ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) {
4298+
ZEND_ASSERT(!(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS));
4299+
}
42944300
}
42954301
ZEND_ASSERT(opline->result_type != IS_TMP_VAR || !Z_ISREF_P(ret));
42964302
#endif
@@ -4422,6 +4428,9 @@ ZEND_VM_HOT_HANDLER(60, ZEND_DO_FCALL, ANY, ANY, SPEC(RETVAL,OBSERVER))
44224428
ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE)
44234429
? Z_ISREF_P(ret) : !Z_ISREF_P(ret));
44244430
zend_verify_internal_func_info(call->func, ret);
4431+
if (ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) {
4432+
ZEND_ASSERT(!(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS));
4433+
}
44254434
}
44264435
ZEND_ASSERT(opline->result_type != IS_TMP_VAR || !Z_ISREF_P(ret));
44274436
#endif
@@ -9131,6 +9140,9 @@ ZEND_VM_HANDLER(158, ZEND_CALL_TRAMPOLINE, ANY, ANY, SPEC(OBSERVER))
91319140
ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE)
91329141
? Z_ISREF_P(ret) : !Z_ISREF_P(ret));
91339142
zend_verify_internal_func_info(call->func, ret);
9143+
if (ZEND_CALL_INFO(call) & ZEND_CALL_DYNAMIC) {
9144+
ZEND_ASSERT(!(call->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS));
9145+
}
91349146
}
91359147
#endif
91369148
ZEND_OBSERVER_FCALL_END(call, EG(exception) ? NULL : ret);

build/gen_stub.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1304,6 +1304,7 @@ public function __construct(
13041304
public ?FunctionOrMethodName $alias,
13051305
private readonly bool $isDeprecated,
13061306
private bool $supportsCompileTimeEval,
1307+
private bool $forbidDynamicCalls,
13071308
public readonly bool $verify,
13081309
public /* readonly */ array $args,
13091310
public /* readonly */ ReturnInfo $return,
@@ -1611,6 +1612,10 @@ private function getArginfoFlagsByPhpVersions(): VersionFlags
16111612
$flags->addForVersionsAbove("ZEND_ACC_NODISCARD", PHP_85_VERSION_ID);
16121613
}
16131614

1615+
if ($this->forbidDynamicCalls) {
1616+
$flags->addForVersionsAbove("ZEND_ACC2_FORBID_DYN_CALLS", PHP_86_VERSION_ID);
1617+
}
1618+
16141619
return $flags;
16151620
}
16161621

@@ -4797,6 +4802,7 @@ function parseFunctionLike(
47974802
$alias = null;
47984803
$isDeprecated = false;
47994804
$supportsCompileTimeEval = false;
4805+
$forbidDynamicCalls = false;
48004806
$verify = true;
48014807
$docReturnType = null;
48024808
$tentativeReturnType = false;
@@ -4812,6 +4818,7 @@ function parseFunctionLike(
48124818
$verify = !array_key_exists('no-verify', $tagMap);
48134819
$tentativeReturnType = array_key_exists('tentative-return-type', $tagMap);
48144820
$supportsCompileTimeEval = array_key_exists('compile-time-eval', $tagMap);
4821+
$forbidDynamicCalls = array_key_exists('forbid-dynamic-calls', $tagMap);
48154822
$isUndocumentable = $isUndocumentable || array_key_exists('undocumentable', $tagMap);
48164823

48174824
foreach ($tags as $tag) {
@@ -4944,6 +4951,7 @@ function parseFunctionLike(
49444951
$alias,
49454952
$isDeprecated,
49464953
$supportsCompileTimeEval,
4954+
$forbidDynamicCalls,
49474955
$verify,
49484956
$args,
49494957
$return,

ext/standard/basic_functions.stub.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1652,6 +1652,7 @@ function extract(array &$array, int $flags = EXTR_OVERWRITE, string $prefix = ""
16521652
* @param array|string $var_names
16531653
* @return array<string, mixed|ref>
16541654
* @refcount 1
1655+
* @forbid-dynamic-calls
16551656
*/
16561657
function compact($var_name, ...$var_names): array {}
16571658

0 commit comments

Comments
 (0)