From: Nikita Popov Date: Fri, 27 Nov 2020 16:40:34 +0000 (+0100) Subject: Don't check for throwing calls in sccp function evaluation X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=0ce9b5f7550ac89c59e541549641512168115594;p=php Don't check for throwing calls in sccp function evaluation We only need to reject functions that could warn (or have runtime dependent behavior). If a function can throw in some cases, just let it and discard the result. --- diff --git a/ext/opcache/Optimizer/sccp.c b/ext/opcache/Optimizer/sccp.c index 9f122296f5..973c1763e3 100644 --- a/ext/opcache/Optimizer/sccp.c +++ b/ext/opcache/Optimizer/sccp.c @@ -781,253 +781,174 @@ static inline int ct_eval_array_key_exists(zval *result, zval *op1, zval *op2) { return SUCCESS; } -/* The functions chosen here are simple to implement and either likely to affect a branch, - * or just happened to be commonly used with constant operands in WP (need to test other - * applications as well, of course). */ -static inline int ct_eval_func_call( - zval *result, zend_string *name, uint32_t num_args, zval **args) { - uint32_t i; - zend_execute_data *execute_data, *prev_execute_data; - zend_function *func; - bool overflow; - - if (num_args == 0) { - if (zend_string_equals_literal(name, "php_sapi_name") - || zend_string_equals_literal(name, "imagetypes") - || zend_string_equals_literal(name, "phpversion")) { - /* pass */ - } else { - return FAILURE; - } - } else if (num_args == 1) { - if (zend_string_equals_literal(name, "chr")) { - zend_long c; - if (Z_TYPE_P(args[0]) != IS_LONG) { - return FAILURE; - } - - c = Z_LVAL_P(args[0]) & 0xff; - ZVAL_CHAR(result, c); - return SUCCESS; - } else if (zend_string_equals_literal(name, "count")) { - if (Z_TYPE_P(args[0]) != IS_ARRAY) { - return FAILURE; - } - - ZVAL_LONG(result, zend_hash_num_elements(Z_ARRVAL_P(args[0]))); - return SUCCESS; - } else if (zend_string_equals_literal(name, "ini_get")) { - zend_ini_entry *ini_entry; - - if (Z_TYPE_P(args[0]) != IS_STRING) { - return FAILURE; - } - - ini_entry = zend_hash_find_ptr(EG(ini_directives), Z_STR_P(args[0])); - if (!ini_entry) { - ZVAL_FALSE(result); - } else if (ini_entry->modifiable != ZEND_INI_SYSTEM) { - return FAILURE; - } else if (ini_entry->value) { - ZVAL_STR_COPY(result, ini_entry->value); - } else { - ZVAL_EMPTY_STRING(result); - } - return SUCCESS; - } else if (zend_string_equals_literal(name, "trim") - || zend_string_equals_literal(name, "rtrim") - || zend_string_equals_literal(name, "ltrim") - || zend_string_equals_literal(name, "str_split") - || zend_string_equals_literal(name, "preg_quote") - || zend_string_equals_literal(name, "base64_encode") - || zend_string_equals_literal(name, "base64_decode") - || zend_string_equals_literal(name, "urlencode") - || zend_string_equals_literal(name, "urldecode") - || zend_string_equals_literal(name, "rawurlencode") - || zend_string_equals_literal(name, "rawurldecode") - || zend_string_equals_literal(name, "php_uname")) { - if (Z_TYPE_P(args[0]) != IS_STRING) { - return FAILURE; - } - /* pass */ - } else if (zend_string_equals_literal(name, "array_keys") - || zend_string_equals_literal(name, "array_values")) { - if (Z_TYPE_P(args[0]) != IS_ARRAY) { - return FAILURE; - } - /* pass */ - } else if (zend_string_equals_literal(name, "array_flip")) { +static zend_bool can_ct_eval_func_call(zend_string *name, uint32_t num_args, zval **args) { + /* Functions that can be evaluated independently of what the arguments are. + * It's okay if these functions throw on invalid arguments, but they should not warn. */ + 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_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_values") + || zend_string_equals_literal(name, "base64_decode") + || zend_string_equals_literal(name, "base64_encode") + || zend_string_equals_literal(name, "imagetypes") + || zend_string_equals_literal(name, "in_array") + || 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_split") + || 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, "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") + ) { + return true; + } + + /* For the following functions we need to check arguments to prevent warnings during + * evaluation. */ + if (num_args == 1) { + if (zend_string_equals_literal(name, "array_flip")) { zval *entry; if (Z_TYPE_P(args[0]) != IS_ARRAY) { - return FAILURE; + return false; } ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(args[0]), entry) { + /* Throws warning for non int/string values. */ if (Z_TYPE_P(entry) != IS_LONG && Z_TYPE_P(entry) != IS_STRING) { - return FAILURE; + return false; } } ZEND_HASH_FOREACH_END(); - /* pass */ - } else if (zend_string_equals_literal(name, "implode")) { + return true; + } + if (zend_string_equals_literal(name, "implode")) { zval *entry; if (Z_TYPE_P(args[0]) != IS_ARRAY) { - return FAILURE; + return false; } ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(args[0]), entry) { + /* May throw warning during conversion to string. */ if (Z_TYPE_P(entry) > IS_STRING) { - return FAILURE; + return false; } } ZEND_HASH_FOREACH_END(); - /* pass */ - } else if (zend_string_equals_literal(name, "serialize")) { - /* pass */ - } else { - return FAILURE; + return true; } - } else if (num_args == 2) { - if (zend_string_equals_literal(name, "in_array")) { - if (Z_TYPE_P(args[1]) != IS_ARRAY) { - return FAILURE; - } - /* pass */ - } else if (zend_string_equals_literal(name, "str_split")) { - if (Z_TYPE_P(args[0]) != IS_STRING - || Z_TYPE_P(args[1]) != IS_LONG - || Z_LVAL_P(args[1]) <= 0) { - return FAILURE; - } - /* pass */ - } else if (zend_string_equals_literal(name, "array_key_exists")) { - if (Z_TYPE_P(args[1]) != IS_ARRAY - || (Z_TYPE_P(args[0]) != IS_LONG - && Z_TYPE_P(args[0]) != IS_STRING - && Z_TYPE_P(args[0]) != IS_NULL)) { - return FAILURE; - } - /* pass */ - } else if (zend_string_equals_literal(name, "trim") - || zend_string_equals_literal(name, "rtrim") - || zend_string_equals_literal(name, "ltrim") - || zend_string_equals_literal(name, "preg_quote")) { - if (Z_TYPE_P(args[0]) != IS_STRING - || Z_TYPE_P(args[1]) != IS_STRING) { - return FAILURE; - } - /* pass */ - } else if (zend_string_equals_literal(name, "str_repeat")) { - if (Z_TYPE_P(args[0]) != IS_STRING - || Z_TYPE_P(args[1]) != IS_LONG - || zend_safe_address(Z_STRLEN_P(args[0]), Z_LVAL_P(args[1]), 0, &overflow) > 64 * 1024 - || overflow) { - return FAILURE; - } - /* pass */ - } else if (zend_string_equals_literal(name, "array_merge") - || zend_string_equals_literal(name, "array_replace") - || zend_string_equals_literal(name, "array_merge_recursive") - || zend_string_equals_literal(name, "array_replace_recursive") - || zend_string_equals_literal(name, "array_diff") - || zend_string_equals_literal(name, "array_diff_assoc") - || zend_string_equals_literal(name, "array_diff_key")) { - for (i = 0; i < num_args; i++) { - if (Z_TYPE_P(args[i]) != IS_ARRAY) { - return FAILURE; - } - } - /* pass */ + return false; + } + + if (num_args == 2) { + if (zend_string_equals_literal(name, "str_repeat")) { + /* Avoid creating overly large strings at compile-time. */ + bool overflow; + return Z_TYPE_P(args[0]) == IS_STRING + && Z_TYPE_P(args[1]) == IS_LONG + && zend_safe_address(Z_STRLEN_P(args[0]), Z_LVAL_P(args[1]), 0, &overflow) < 64 * 1024 + && !overflow; } else if (zend_string_equals_literal(name, "implode")) { zval *entry; if ((Z_TYPE_P(args[0]) != IS_STRING || Z_TYPE_P(args[1]) != IS_ARRAY) && (Z_TYPE_P(args[0]) != IS_ARRAY || Z_TYPE_P(args[1]) != IS_STRING)) { - return FAILURE; + return false; } + /* May throw warning during conversion to string. */ if (Z_TYPE_P(args[0]) == IS_ARRAY) { ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(args[0]), entry) { if (Z_TYPE_P(entry) > IS_STRING) { - return FAILURE; + return false; } } ZEND_HASH_FOREACH_END(); } else { ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(args[1]), entry) { if (Z_TYPE_P(entry) > IS_STRING) { - return FAILURE; + return false; } } ZEND_HASH_FOREACH_END(); } - /* pass */ - } else if (zend_string_equals_literal(name, "strpos") - || zend_string_equals_literal(name, "str_contains") - || zend_string_equals_literal(name, "str_starts_with") - || zend_string_equals_literal(name, "str_ends_with") - || zend_string_equals_literal(name, "version_compare")) { - if (Z_TYPE_P(args[0]) != IS_STRING - || Z_TYPE_P(args[1]) != IS_STRING) { - return FAILURE; - } - /* pass */ - } else if (zend_string_equals_literal(name, "substr")) { - if (Z_TYPE_P(args[0]) != IS_STRING - || Z_TYPE_P(args[1]) != IS_LONG) { - return FAILURE; - } - /* pass */ - } else if (zend_string_equals_literal(name, "pow")) { - if ((Z_TYPE_P(args[0]) != IS_LONG && Z_TYPE_P(args[0]) != IS_DOUBLE) - || (Z_TYPE_P(args[1]) != IS_LONG && Z_TYPE_P(args[1]) != IS_DOUBLE)) { - return FAILURE; - } - /* pass */ - } else { - return FAILURE; + return true; } - } else if (num_args == 3) { - if (zend_string_equals_literal(name, "in_array")) { - if (Z_TYPE_P(args[1]) != IS_ARRAY - || (Z_TYPE_P(args[2]) != IS_FALSE - && Z_TYPE_P(args[2]) != IS_TRUE)) { + return false; + } + + return false; +} + +/* The functions chosen here are simple to implement and either likely to affect a branch, + * or just happened to be commonly used with constant operands in WP (need to test other + * applications as well, of course). */ +static inline int ct_eval_func_call( + zval *result, zend_string *name, uint32_t num_args, zval **args) { + uint32_t i; + zend_execute_data *execute_data, *prev_execute_data; + zend_function *func = zend_hash_find_ptr(CG(function_table), name); + if (!func || func->type != ZEND_INTERNAL_FUNCTION) { + return FAILURE; + } + + if (num_args == 1) { + /* Handle a few functions for which we manually implement evaluation here. */ + if (zend_string_equals_literal(name, "chr")) { + zend_long c; + if (Z_TYPE_P(args[0]) != IS_LONG) { return FAILURE; } - /* pass */ - } else if (zend_string_equals_literal(name, "array_merge") - || zend_string_equals_literal(name, "array_replace") - || zend_string_equals_literal(name, "array_merge_recursive") - || zend_string_equals_literal(name, "array_replace_recursive") - || zend_string_equals_literal(name, "array_diff") - || zend_string_equals_literal(name, "array_diff_assoc") - || zend_string_equals_literal(name, "array_diff_key")) { - for (i = 0; i < num_args; i++) { - if (Z_TYPE_P(args[i]) != IS_ARRAY) { - return FAILURE; - } + + c = Z_LVAL_P(args[0]) & 0xff; + ZVAL_CHAR(result, c); + return SUCCESS; + } else if (zend_string_equals_literal(name, "count")) { + if (Z_TYPE_P(args[0]) != IS_ARRAY) { + return FAILURE; } - /* pass */ - } else if (zend_string_equals_literal(name, "version_compare")) { - if (Z_TYPE_P(args[0]) != IS_STRING - || Z_TYPE_P(args[1]) != IS_STRING - || Z_TYPE_P(args[2]) != IS_STRING) { + + ZVAL_LONG(result, zend_hash_num_elements(Z_ARRVAL_P(args[0]))); + return SUCCESS; + } else if (zend_string_equals_literal(name, "ini_get")) { + zend_ini_entry *ini_entry; + + if (Z_TYPE_P(args[0]) != IS_STRING) { return FAILURE; } - /* pass */ - } else if (zend_string_equals_literal(name, "substr")) { - if (Z_TYPE_P(args[0]) != IS_STRING - || Z_TYPE_P(args[1]) != IS_LONG - || Z_TYPE_P(args[2]) != IS_LONG) { + + ini_entry = zend_hash_find_ptr(EG(ini_directives), Z_STR_P(args[0])); + if (!ini_entry) { + ZVAL_FALSE(result); + } else if (ini_entry->modifiable != ZEND_INI_SYSTEM) { return FAILURE; + } else if (ini_entry->value) { + ZVAL_STR_COPY(result, ini_entry->value); + } else { + ZVAL_EMPTY_STRING(result); } - /* pass */ - } else { - return FAILURE; + return SUCCESS; } - } else { - return FAILURE; } - func = zend_hash_find_ptr(CG(function_table), name); - if (!func || func->type != ZEND_INTERNAL_FUNCTION) { + if (!can_ct_eval_func_call(name, num_args, args)) { return FAILURE; }