Skip to content

Allow internal functions to declare if they support compile-time evaluation. #7780

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 11 additions & 49 deletions Zend/Optimizer/sccp.c
Original file line number Diff line number Diff line change
Expand Up @@ -799,59 +799,21 @@ static inline zend_result ct_eval_array_key_exists(zval *result, zval *op1, zval
return SUCCESS;
}

static bool can_ct_eval_func_call(zend_string *name, uint32_t num_args, zval **args) {
/* Functions in this list must always produce the same result for the same arguments,
static bool can_ct_eval_func_call(zend_function *func, zend_string *name, uint32_t num_args, zval **args) {
/* Precondition: func->type == ZEND_INTERNAL_FUNCTION, this is a global function */
/* Functions setting ZEND_ACC_COMPILE_TIME_EVAL (@compile-time-eval) must always produce the same result for the same arguments,
* and have no dependence on global state (such as locales). It is okay if they throw
* or warn on invalid arguments, as we detect this and will discard the evaluation result. */
if (false
|| zend_string_equals_literal(name, "array_diff")
|| zend_string_equals_literal(name, "array_diff_assoc")
|| zend_string_equals_literal(name, "array_diff_key")
|| zend_string_equals_literal(name, "array_flip")
|| zend_string_equals_literal(name, "array_is_list")
|| zend_string_equals_literal(name, "array_key_exists")
|| zend_string_equals_literal(name, "array_keys")
|| zend_string_equals_literal(name, "array_merge")
|| zend_string_equals_literal(name, "array_merge_recursive")
|| zend_string_equals_literal(name, "array_replace")
|| zend_string_equals_literal(name, "array_replace_recursive")
|| zend_string_equals_literal(name, "array_unique")
|| zend_string_equals_literal(name, "array_values")
|| zend_string_equals_literal(name, "base64_decode")
|| zend_string_equals_literal(name, "base64_encode")
if (func->common.fn_flags & ZEND_ACC_COMPILE_TIME_EVAL) {
/* This has @compile-time-eval in stub info and uses a macro such as ZEND_SUPPORTS_COMPILE_TIME_EVAL_FE */
return true;
}
#ifndef ZEND_WIN32
/* On Windows this function may be code page dependent. */
|| zend_string_equals_literal(name, "dirname")
#endif
|| zend_string_equals_literal(name, "explode")
|| zend_string_equals_literal(name, "imagetypes")
|| zend_string_equals_literal(name, "in_array")
|| zend_string_equals_literal(name, "implode")
|| zend_string_equals_literal(name, "ltrim")
|| zend_string_equals_literal(name, "php_sapi_name")
|| zend_string_equals_literal(name, "php_uname")
|| zend_string_equals_literal(name, "phpversion")
|| zend_string_equals_literal(name, "pow")
|| zend_string_equals_literal(name, "preg_quote")
|| zend_string_equals_literal(name, "rawurldecode")
|| zend_string_equals_literal(name, "rawurlencode")
|| zend_string_equals_literal(name, "rtrim")
|| zend_string_equals_literal(name, "serialize")
|| zend_string_equals_literal(name, "str_contains")
|| zend_string_equals_literal(name, "str_ends_with")
|| zend_string_equals_literal(name, "str_replace")
|| zend_string_equals_literal(name, "str_split")
|| zend_string_equals_literal(name, "str_starts_with")
|| zend_string_equals_literal(name, "strpos")
|| zend_string_equals_literal(name, "strstr")
|| zend_string_equals_literal(name, "substr")
|| zend_string_equals_literal(name, "trim")
|| zend_string_equals_literal(name, "urldecode")
|| zend_string_equals_literal(name, "urlencode")
|| zend_string_equals_literal(name, "version_compare")
) {
/* On Windows this function may be code page dependent. */
if (zend_string_equals_literal(name, "dirname")) {
return true;
}
#endif

if (num_args == 2) {
if (zend_string_equals_literal(name, "str_repeat")) {
Expand Down Expand Up @@ -918,7 +880,7 @@ static inline zend_result ct_eval_func_call(
}
}

if (!can_ct_eval_func_call(name, num_args, args)) {
if (!can_ct_eval_func_call(func, name, num_args, args)) {
return FAILURE;
}

Expand Down
8 changes: 7 additions & 1 deletion Zend/tests/bug38623.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@
Bug #38623 (leaks in a tricky code with switch() and exceptions)
--FILE--
<?php
/* This used to use strtolower, but opcache evaluates that at compile time as of php 8.2 https://wiki.php.net/rfc/strtolower-ascii */
function create_refcounted_string() {
$x = 'bpache';
$x[0] = 'a';
return $x;
}
try {
switch(strtolower("apache")) {
switch(create_refcounted_string()) {
case "apache":
throw new Exception("test");
break;
Expand Down
11 changes: 11 additions & 0 deletions Zend/zend_API.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,17 @@ typedef struct _zend_fcall_info_cache {
#define ZEND_NS_FENTRY(ns, zend_name, name, arg_info, flags) ZEND_RAW_FENTRY(ZEND_NS_NAME(ns, #zend_name), name, arg_info, flags)

#define ZEND_NS_RAW_FENTRY(ns, zend_name, name, arg_info, flags) ZEND_RAW_FENTRY(ZEND_NS_NAME(ns, zend_name), name, arg_info, flags)
/**
* Note that if you are asserting that a function is compile-time evaluable, you are asserting that
*
* 1. The function will always have the same result for the same arguments
* 2. The function does not depend on global state such as ini settings or locale (e.g. mb_strtolower), number_format(), etc.
* 3. The function does not have side effects. It is okay if they throw
* or warn on invalid arguments, as we detect this and will discard the evaluation result.
* 4. The function will not take an unreasonable amount of time or memory to compute on code that may be seen in practice.
* (e.g. str_repeat is special cased to check the length instead of using this)
*/
#define ZEND_SUPPORTS_COMPILE_TIME_EVAL_FE(name, arg_info) ZEND_RAW_FENTRY(#name, zif_##name, arg_info, ZEND_ACC_COMPILE_TIME_EVAL)

/* Same as ZEND_NS_NAMED_FE */
#define ZEND_NS_RAW_NAMED_FE(ns, zend_name, name, arg_info) ZEND_NS_RAW_FENTRY(ns, #zend_name, name, arg_info, 0)
Expand Down
4 changes: 4 additions & 0 deletions Zend/zend_builtin_functions.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,16 @@ function func_get_args(): array {}

function strlen(string $string): int {}

/** @compile-time-eval */
function strcmp(string $string1, string $string2): int {}

/** @compile-time-eval */
function strncmp(string $string1, string $string2, int $length): int {}

/** @compile-time-eval */
function strcasecmp(string $string1, string $string2): int {}

/** @compile-time-eval */
function strncasecmp(string $string1, string $string2, int $length): int {}

function error_reporting(?int $error_level = null): int {}
Expand Down
10 changes: 5 additions & 5 deletions Zend/zend_builtin_functions_arginfo.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: f87d92c002674c431827895a8d8b3a5da3b95482 */
* Stub hash: 69dcb08ae12b6acbba872f7de5018ca5c0aaf669 */

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_version, 0, 0, IS_STRING, 0)
ZEND_END_ARG_INFO()
Expand Down Expand Up @@ -283,10 +283,10 @@ static const zend_function_entry ext_functions[] = {
ZEND_FE(func_get_arg, arginfo_func_get_arg)
ZEND_FE(func_get_args, arginfo_func_get_args)
ZEND_FE(strlen, arginfo_strlen)
ZEND_FE(strcmp, arginfo_strcmp)
ZEND_FE(strncmp, arginfo_strncmp)
ZEND_FE(strcasecmp, arginfo_strcasecmp)
ZEND_FE(strncasecmp, arginfo_strncasecmp)
ZEND_SUPPORTS_COMPILE_TIME_EVAL_FE(strcmp, arginfo_strcmp)
ZEND_SUPPORTS_COMPILE_TIME_EVAL_FE(strncmp, arginfo_strncmp)
ZEND_SUPPORTS_COMPILE_TIME_EVAL_FE(strcasecmp, arginfo_strcasecmp)
ZEND_SUPPORTS_COMPILE_TIME_EVAL_FE(strncasecmp, arginfo_strncasecmp)
ZEND_FE(error_reporting, arginfo_error_reporting)
ZEND_FE(define, arginfo_define)
ZEND_FE(defined, arginfo_defined)
Expand Down
5 changes: 4 additions & 1 deletion Zend/zend_compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ typedef struct _zend_oparray_context {
/* Class cannot be serialized or unserialized | | | */
#define ZEND_ACC_NOT_SERIALIZABLE (1 << 29) /* X | | | */
/* | | | */
/* Function Flags (unused: 27-30) | | | */
/* Function Flags (unused: 28-30) | | | */
/* ============== | | | */
/* | | | */
/* deprecation flag | | | */
Expand Down Expand Up @@ -357,6 +357,9 @@ typedef struct _zend_oparray_context {
/* method flag used by Closure::__invoke() (int only) | | | */
#define ZEND_ACC_USER_ARG_INFO (1 << 26) /* | X | | */
/* | | | */
/* supports opcache compile-time evaluation (funcs) | | | */
#define ZEND_ACC_COMPILE_TIME_EVAL (1 << 27) /* | X | | */
/* | | | */
/* op_array uses strict mode types | | | */
#define ZEND_ACC_STRICT_TYPES (1U << 31) /* | X | | */

Expand Down
12 changes: 12 additions & 0 deletions build/gen_stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,8 @@ class FuncInfo {
/** @var bool */
public $isDeprecated;
/** @var bool */
public $supportsCompileTimeEval;
/** @var bool */
public $verify;
/** @var ArgInfo[] */
public $args;
Expand All @@ -979,6 +981,7 @@ public function __construct(
?string $aliasType,
?FunctionOrMethodName $alias,
bool $isDeprecated,
bool $supportsCompileTimeEval,
bool $verify,
array $args,
ReturnInfo $return,
Expand All @@ -991,6 +994,7 @@ public function __construct(
$this->aliasType = $aliasType;
$this->alias = $alias;
$this->isDeprecated = $isDeprecated;
$this->supportsCompileTimeEval = $supportsCompileTimeEval;
$this->verify = $verify;
$this->args = $args;
$this->return = $return;
Expand Down Expand Up @@ -1155,6 +1159,10 @@ public function getFunctionEntry(): string {
"\tZEND_NS_FE(\"%s\", %s, %s)\n",
addslashes($namespace), $declarationName, $this->getArgInfoName());
} else {
if ($this->supportsCompileTimeEval) {
return sprintf(
"\tZEND_SUPPORTS_COMPILE_TIME_EVAL_FE(%s, %s)\n", $declarationName, $this->getArgInfoName());
}
return sprintf("\tZEND_FE(%s, %s)\n", $declarationName, $this->getArgInfoName());
}
} else {
Expand Down Expand Up @@ -2232,6 +2240,7 @@ function parseFunctionLike(
$aliasType = null;
$alias = null;
$isDeprecated = false;
$supportsCompileTimeEval = false;
$verify = true;
$docReturnType = null;
$tentativeReturnType = false;
Expand Down Expand Up @@ -2267,6 +2276,8 @@ function parseFunctionLike(
$docParamTypes[$tag->getVariableName()] = $tag->getType();
} else if ($tag->name === 'refcount') {
$refcount = $tag->getValue();
} else if ($tag->name === 'compile-time-eval') {
$supportsCompileTimeEval = true;
}
}
}
Expand Down Expand Up @@ -2355,6 +2366,7 @@ function parseFunctionLike(
$aliasType,
$alias,
$isDeprecated,
$supportsCompileTimeEval,
$verify,
$args,
$return,
Expand Down
1 change: 1 addition & 0 deletions ext/pcre/php_pcre.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ function preg_replace_callback_array(array $pattern, string|array $subject, int
*/
function preg_split(string $pattern, string $subject, int $limit = -1, int $flags = 0): array|false {}

/** @compile-time-eval */
function preg_quote(string $str, ?string $delimiter = null): string {}

/** @refcount 1 */
Expand Down
4 changes: 2 additions & 2 deletions ext/pcre/php_pcre_arginfo.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: bc6f31ac17d4f5d1a60dd3dad5f671058f40a224 */
* Stub hash: 39a19378fb1f1aca34bfdd483f5d3095558f0e09 */

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_preg_match, 0, 2, MAY_BE_LONG|MAY_BE_FALSE)
ZEND_ARG_TYPE_INFO(0, pattern, IS_STRING, 0)
Expand Down Expand Up @@ -84,7 +84,7 @@ static const zend_function_entry ext_functions[] = {
ZEND_FE(preg_replace_callback, arginfo_preg_replace_callback)
ZEND_FE(preg_replace_callback_array, arginfo_preg_replace_callback_array)
ZEND_FE(preg_split, arginfo_preg_split)
ZEND_FE(preg_quote, arginfo_preg_quote)
ZEND_SUPPORTS_COMPILE_TIME_EVAL_FE(preg_quote, arginfo_preg_quote)
ZEND_FE(preg_grep, arginfo_preg_grep)
ZEND_FE(preg_last_error, arginfo_preg_last_error)
ZEND_FE(preg_last_error_msg, arginfo_preg_last_error_msg)
Expand Down
Loading