diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 43e21cafd564..75e016554c8a 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -530,6 +530,39 @@ ZEND_API bool ZEND_FASTCALL zend_parse_arg_class(zval *arg, zend_class_entry **p } /* }}} */ +ZEND_API bool ZEND_FASTCALL zend_parse_arg_derived_class(zval *arg, zend_class_entry **ce, const zend_class_entry *base_ce, uint32_t num, bool check_null) { + if (check_null && Z_TYPE_P(arg) == IS_NULL) { + *ce = NULL; + return true; + } + + /* Only accept string and Stringable(?) as int/foat/bool are not valid class names */ + if (UNEXPECTED(Z_TYPE_P(arg) != IS_STRING)) { + if (Z_TYPE_P(arg) != IS_OBJECT || !zend_parse_arg_str_slow(arg, num)) { + *ce = NULL; + zend_wrong_parameter_type_error(num, check_null ? Z_EXPECTED_CLASS_NAME_OR_NULL : Z_EXPECTED_CLASS_NAME, arg); + return false; + } + /* Object was converted to string */ + ZEND_ASSERT(Z_TYPE_P(arg) == IS_STRING); + } + zend_string *class_name = Z_STR_P(arg); + zend_class_entry *user_ce = zend_lookup_class(class_name); + if (UNEXPECTED(!user_ce)) { + *ce = NULL; + zend_wrong_parameter_type_error(num, check_null ? Z_EXPECTED_CLASS_NAME_OR_NULL : Z_EXPECTED_CLASS_NAME, arg); + return false; + } + if (UNEXPECTED(!instanceof_function(user_ce, base_ce))) { + zend_argument_type_error(num, "must be a class name derived from %s%s, \"%s\" given", + ZSTR_VAL(base_ce->name), check_null ? " or null" : "", ZSTR_VAL(class_name)); + *ce = NULL; + return false; + } + *ce = user_ce; + return true; +} + static ZEND_COLD bool zend_null_arg_deprecated(const char *fallback_type, uint32_t arg_num) { const zend_function *func = zend_active_function(); ZEND_ASSERT(arg_num > 0); @@ -1021,6 +1054,10 @@ static zend_expected_type zend_parse_arg_impl(zval *arg, va_list *va, const char { zend_class_entry **pce = va_arg(*va, zend_class_entry **); const zend_class_entry *ce_base = *pce; + if (ce_base) { + zend_throw_error(NULL, "Cannot use 'C' specifier anymore with a derived class!"); + return Z_EXPECTED_CLASS_NAME_OR_NULL; + } if (check_null && Z_TYPE_P(arg) == IS_NULL) { *pce = NULL; diff --git a/Zend/zend_API.h b/Zend/zend_API.h index da871e1eb390..2d6cdf01665b 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -1765,18 +1765,31 @@ ZEND_API ZEND_COLD void zend_class_redeclaration_error_ex(int type, zend_string Z_PARAM_BOOL_EX(dest, is_null, 1, 0) /* old "C" */ -#define Z_PARAM_CLASS_EX(dest, check_null, deref) \ - Z_PARAM_PROLOGUE(deref, 0); \ +#define Z_PARAM_CLASS_EX(dest, check_null) \ + Z_PARAM_PROLOGUE(0, 0); \ if (UNEXPECTED(!zend_parse_arg_class(_arg, &dest, _i, check_null))) { \ _error_code = ZPP_ERROR_FAILURE; \ break; \ } #define Z_PARAM_CLASS(dest) \ - Z_PARAM_CLASS_EX(dest, 0, 0) + Z_PARAM_CLASS_EX(dest, 0) #define Z_PARAM_CLASS_OR_NULL(dest) \ - Z_PARAM_CLASS_EX(dest, 1, 0) + Z_PARAM_CLASS_EX(dest, 1) + +#define Z_PARAM_DERIVED_CLASS_NAME_EX(dest, base_ce, allow_null) \ + Z_PARAM_PROLOGUE(0, 0); \ + if (UNEXPECTED(!zend_parse_arg_derived_class(_arg, &dest, base_ce, _i, allow_null))) { \ + _error_code = ZPP_ERROR_FAILURE; \ + break; \ + } + +#define Z_PARAM_DERIVED_CLASS_NAME(dest, base_ce) \ + Z_PARAM_DERIVED_CLASS_NAME_EX(dest, base_ce, false) + +#define Z_PARAM_DERIVED_CLASS_NAME_OR_NULL(dest, base_ce) \ + Z_PARAM_DERIVED_CLASS_NAME_EX(dest, base_ce, true) #define Z_PARAM_OBJ_OR_CLASS_NAME_EX(dest, allow_null) \ Z_PARAM_PROLOGUE(0, 0); \ @@ -2217,6 +2230,7 @@ typedef enum zpp_parse_bool_status { } zpp_parse_bool_status; ZEND_API bool ZEND_FASTCALL zend_parse_arg_class(zval *arg, zend_class_entry **pce, uint32_t num, bool check_null); +ZEND_API bool ZEND_FASTCALL zend_parse_arg_derived_class(zval *arg, zend_class_entry **ce, const zend_class_entry *base_ce, uint32_t num, bool check_null); ZEND_API zpp_parse_bool_status ZEND_FASTCALL zend_parse_arg_bool_slow(const zval *arg, uint32_t arg_num); ZEND_API zpp_parse_bool_status ZEND_FASTCALL zend_parse_arg_bool_weak(const zval *arg, uint32_t arg_num); ZEND_API bool ZEND_FASTCALL zend_parse_arg_long_slow(const zval *arg, zend_long *dest, uint32_t arg_num); diff --git a/ext/dom/document.c b/ext/dom/document.c index bad0dd9feb1b..6bf808d33791 100644 --- a/ext/dom/document.c +++ b/ext/dom/document.c @@ -2287,32 +2287,33 @@ PHP_METHOD(DOMDocument, saveHTML) /* {{{ Register extended class used to create base node type */ static void dom_document_register_node_class(INTERNAL_FUNCTION_PARAMETERS, bool modern) { - zend_class_entry *basece = dom_get_node_ce(modern), *ce = NULL; + zend_class_entry *base_ce, *ce = NULL; dom_object *intern; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "CC!", &basece, &ce) == FAILURE) { - RETURN_THROWS(); - } + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_DERIVED_CLASS_NAME(base_ce, dom_get_node_ce(modern)) + Z_PARAM_CLASS_OR_NULL(ce) + ZEND_PARSE_PARAMETERS_END(); - if (basece->ce_flags & ZEND_ACC_ABSTRACT) { + if (base_ce->ce_flags & ZEND_ACC_ABSTRACT) { zend_argument_value_error(1, "must not be an abstract class"); RETURN_THROWS(); } - if (ce == NULL || instanceof_function(ce, basece)) { + if (ce == NULL || instanceof_function(ce, base_ce)) { if (UNEXPECTED(ce != NULL && (ce->ce_flags & ZEND_ACC_ABSTRACT))) { zend_argument_value_error(2, "must not be an abstract class"); RETURN_THROWS(); } DOM_GET_THIS_INTERN(intern); - dom_set_doc_classmap(intern->document, basece, ce); + dom_set_doc_classmap(intern->document, base_ce, ce); if (!modern) { RETVAL_TRUE; } return; } - zend_argument_error(NULL, 2, "must be a class name derived from %s or null, %s given", ZSTR_VAL(basece->name), ZSTR_VAL(ce->name)); + zend_argument_error(NULL, 2, "must be a class name derived from %s or null, %s given", ZSTR_VAL(base_ce->name), ZSTR_VAL(ce->name)); RETURN_THROWS(); } diff --git a/ext/simplexml/simplexml.c b/ext/simplexml/simplexml.c index 94c538a40488..eb84bafc6ec1 100644 --- a/ext/simplexml/simplexml.c +++ b/ext/simplexml/simplexml.c @@ -2188,13 +2188,18 @@ PHP_FUNCTION(simplexml_load_file) xmlDocPtr docp; zend_string *ns = zend_empty_string; zend_long options = 0; - zend_class_entry *ce= ce_SimpleXMLElement; + zend_class_entry *ce = NULL; zend_function *fptr_count; bool isprefix = false; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "p|C!lSb", &filename, &filename_len, &ce, &options, &ns, &isprefix) == FAILURE) { - RETURN_THROWS(); - } + ZEND_PARSE_PARAMETERS_START(1, 5) + Z_PARAM_PATH(filename, filename_len) + Z_PARAM_OPTIONAL + Z_PARAM_DERIVED_CLASS_NAME_OR_NULL(ce, ce_SimpleXMLElement) + Z_PARAM_LONG(options) + Z_PARAM_STR(ns) + Z_PARAM_BOOL(isprefix) + ZEND_PARSE_PARAMETERS_END(); if (ZEND_LONG_EXCEEDS_INT(options)) { zend_argument_value_error(3, "is too large"); @@ -2234,13 +2239,18 @@ PHP_FUNCTION(simplexml_load_string) xmlDocPtr docp; zend_string *ns = zend_empty_string; zend_long options = 0; - zend_class_entry *ce= ce_SimpleXMLElement; + zend_class_entry *ce = NULL; zend_function *fptr_count; bool isprefix = false; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|C!lSb", &data, &data_len, &ce, &options, &ns, &isprefix) == FAILURE) { - RETURN_THROWS(); - } + ZEND_PARSE_PARAMETERS_START(1, 5) + Z_PARAM_STRING(data, data_len) + Z_PARAM_OPTIONAL + Z_PARAM_DERIVED_CLASS_NAME_OR_NULL(ce, ce_SimpleXMLElement) + Z_PARAM_LONG(options) + Z_PARAM_STR(ns) + Z_PARAM_BOOL(isprefix) + ZEND_PARSE_PARAMETERS_END(); if (ZEND_SIZE_T_INT_OVFL(data_len)) { zend_argument_value_error(1, "is too long"); diff --git a/ext/spl/spl_array.c b/ext/spl/spl_array.c index 257a7077a208..ade99e9b958f 100644 --- a/ext/spl/spl_array.c +++ b/ext/spl/spl_array.c @@ -995,15 +995,18 @@ PHP_METHOD(ArrayObject, __construct) spl_array_object *intern; zval *array; zend_long ar_flags = 0; - zend_class_entry *ce_get_iterator = spl_ce_ArrayIterator; + zend_class_entry *ce_get_iterator; if (ZEND_NUM_ARGS() == 0) { return; /* nothing to do */ } - if (zend_parse_parameters(ZEND_NUM_ARGS(), "|AlC", &array, &ar_flags, &ce_get_iterator) == FAILURE) { - RETURN_THROWS(); - } + ZEND_PARSE_PARAMETERS_START(0, 3) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY_OR_OBJECT(array) + Z_PARAM_LONG(ar_flags) + Z_PARAM_DERIVED_CLASS_NAME(ce_get_iterator, spl_ce_ArrayIterator) + ZEND_PARSE_PARAMETERS_END(); intern = Z_SPLARRAY_P(object); @@ -1022,10 +1025,10 @@ PHP_METHOD(ArrayObject, setIteratorClass) { zval *object = ZEND_THIS; spl_array_object *intern = Z_SPLARRAY_P(object); - zend_class_entry *ce_get_iterator = spl_ce_ArrayIterator; + zend_class_entry *ce_get_iterator; ZEND_PARSE_PARAMETERS_START(1, 1) - Z_PARAM_CLASS(ce_get_iterator) + Z_PARAM_DERIVED_CLASS_NAME(ce_get_iterator, spl_ce_ArrayIterator) ZEND_PARSE_PARAMETERS_END(); intern->ce_get_iterator = ce_get_iterator; diff --git a/ext/spl/spl_directory.c b/ext/spl/spl_directory.c index daad1a4908f2..695e2aa93700 100644 --- a/ext/spl/spl_directory.c +++ b/ext/spl/spl_directory.c @@ -1269,9 +1269,10 @@ PHP_METHOD(SplFileInfo, setFileClass) spl_filesystem_object *intern = spl_filesystem_from_obj(Z_OBJ_P(ZEND_THIS)); zend_class_entry *ce = spl_ce_SplFileObject; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "|C", &ce) == FAILURE) { - RETURN_THROWS(); - } + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_DERIVED_CLASS_NAME(ce, spl_ce_SplFileObject) + ZEND_PARSE_PARAMETERS_END(); intern->file_class = ce; } @@ -1283,9 +1284,10 @@ PHP_METHOD(SplFileInfo, setInfoClass) spl_filesystem_object *intern = spl_filesystem_from_obj(Z_OBJ_P(ZEND_THIS)); zend_class_entry *ce = spl_ce_SplFileInfo; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "|C", &ce) == FAILURE) { - RETURN_THROWS(); - } + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_DERIVED_CLASS_NAME(ce, spl_ce_SplFileInfo) + ZEND_PARSE_PARAMETERS_END(); intern->info_class = ce; } @@ -1297,9 +1299,10 @@ PHP_METHOD(SplFileInfo, getFileInfo) spl_filesystem_object *intern = spl_filesystem_from_obj(Z_OBJ_P(ZEND_THIS)); zend_class_entry *ce = intern->info_class; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "|C!", &ce) == FAILURE) { - RETURN_THROWS(); - } + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_DERIVED_CLASS_NAME_OR_NULL(ce, intern->info_class) + ZEND_PARSE_PARAMETERS_END(); spl_filesystem_object_create_type(ZEND_NUM_ARGS(), intern, SPL_FS_INFO, ce, return_value); } @@ -1312,15 +1315,13 @@ PHP_METHOD(SplFileInfo, getPathInfo) zend_class_entry *ce = NULL; zend_string *path; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "|C!", &ce) == FAILURE) { - RETURN_THROWS(); - } + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_DERIVED_CLASS_NAME_OR_NULL(ce, spl_ce_SplFileInfo) + ZEND_PARSE_PARAMETERS_END(); if (ce == NULL) { ce = intern->info_class; - } else if (!instanceof_function(ce, spl_ce_SplFileInfo)) { - zend_argument_type_error(1, "must be a class name derived from %s or null, %s given", ZSTR_VAL(spl_ce_SplFileInfo->name), ZSTR_VAL(ce->name)); - RETURN_THROWS(); } path = spl_filesystem_object_get_pathname(intern); diff --git a/ext/spl/tests/ArrayObject/arrayObject___construct_error1.phpt b/ext/spl/tests/ArrayObject/arrayObject___construct_error1.phpt index 29500a5ca672..15d07cd7909f 100644 --- a/ext/spl/tests/ArrayObject/arrayObject___construct_error1.phpt +++ b/ext/spl/tests/ArrayObject/arrayObject___construct_error1.phpt @@ -22,4 +22,4 @@ try { Bad iterator type: TypeError: ArrayObject::__construct(): Argument #3 ($iteratorClass) must be a class name derived from ArrayIterator, "Exception" given Non-existent class: -TypeError: ArrayObject::__construct(): Argument #3 ($iteratorClass) must be a class name derived from ArrayIterator, "nonExistentClassName" given +TypeError: ArrayObject::__construct(): Argument #3 ($iteratorClass) must be a valid class name, "nonExistentClassName" given diff --git a/ext/spl/tests/ArrayObject/arrayObject_setIteratorClass_error1.phpt b/ext/spl/tests/ArrayObject/arrayObject_setIteratorClass_error1.phpt index d1257ab0a6b7..c1da247dd7ab 100644 --- a/ext/spl/tests/ArrayObject/arrayObject_setIteratorClass_error1.phpt +++ b/ext/spl/tests/ArrayObject/arrayObject_setIteratorClass_error1.phpt @@ -43,7 +43,7 @@ try { ?> --EXPECT-- -TypeError: ArrayObject::setIteratorClass(): Argument #1 ($iteratorClass) must be a class name derived from ArrayIterator, "nonExistentClass" given +TypeError: ArrayObject::setIteratorClass(): Argument #1 ($iteratorClass) must be a valid class name, "nonExistentClass" given TypeError: ArrayObject::setIteratorClass(): Argument #1 ($iteratorClass) must be a class name derived from ArrayIterator, "stdClass" given -TypeError: ArrayObject::__construct(): Argument #3 ($iteratorClass) must be a class name derived from ArrayIterator, "nonExistentClass" given +TypeError: ArrayObject::__construct(): Argument #3 ($iteratorClass) must be a valid class name, "nonExistentClass" given TypeError: ArrayObject::__construct(): Argument #3 ($iteratorClass) must be a class name derived from ArrayIterator, "stdClass" given diff --git a/ext/spl/tests/gh17516.phpt b/ext/spl/tests/gh17516.phpt index 669f2286abdd..5bd80a7c476c 100644 --- a/ext/spl/tests/gh17516.phpt +++ b/ext/spl/tests/gh17516.phpt @@ -21,4 +21,4 @@ object(SplFileInfoChild)#2 (2) { ["fileName":"SplFileInfo":private]=> string(4) "php:" } -TypeError: SplFileInfo::getPathInfo(): Argument #1 ($class) must be a class name derived from SplFileInfo or null, BadSplFileInfo given +TypeError: SplFileInfo::getPathInfo(): Argument #1 ($class) must be a class name derived from SplFileInfo or null, "BadSplFileInfo" given diff --git a/scripts/dev/check_parameters.php b/scripts/dev/check_parameters.php index 83c19d0b4ef6..c0aef1ad3b70 100755 --- a/scripts/dev/check_parameters.php +++ b/scripts/dev/check_parameters.php @@ -16,32 +16,31 @@ define('REPORT_LEVEL', 1); // 0 reports less false-positives. up to level 5. define('VERSION', '7.0'); // minimum is 7.0 -define('PHPDIR', realpath(dirname(__FILE__) . '/../..')); +define('PHPDIR', realpath(dirname(__FILE__, 3))); // be sure you have enough memory and stack for PHP. pcre will push the limits! ini_set('pcre.backtrack_limit', 10000000); // ------------------------ end of config ---------------------------- -$API_params = array( - 'a' => array('zval**'), // array - 'A' => array('zval**'), // array or object - 'b' => array('bool*'), // boolean - 'd' => array('double*'), // double - 'f' => array('zend_fcall_info*', 'zend_fcall_info_cache*'), // function - 'h' => array('HashTable**'), // array as an HashTable* - 'H' => array('HashTable**'), // array or HASH_OF(object) - 'l' => array('zend_long*'), // long - //TODO 'L' => array('zend_long*, '), // long - 'o' => array('zval**'), //object - 'O' => array('zval**', 'zend_class_entry*'), // object of given type - 'P' => array('zend_string**'), // valid path - 'r' => array('zval**'), // resource - 'S' => array('zend_string**'), // string - 'z' => array('zval**'), // zval* - 'Z' => array('zval***') // zval** +const ZPP_SPECIFIER_PARAMS = [ + 'a' => ['zval**'], // array + 'A' => ['zval**'], // array or object + 'b' => ['bool*'], // boolean + 'd' => ['double*'], // double + 'f' => ['zend_fcall_info*', 'zend_fcall_info_cache*'], // function + 'F' => ['zend_fcall_info*', 'zend_fcall_info_cache*'], // function + 'h' => ['HashTable**'], // array as an HashTable* + 'H' => ['HashTable**'], // array or HASH_OF(object) + 'l' => ['zend_long*'], // long + 'o' => ['zval**'], //object + 'O' => ['zval**', 'zend_class_entry*'], // object of given type + 'P' => ['zend_string**'], // valid path + 'r' => ['zval**'], // resource + 'S' => ['zend_string**'], // string + 'z' => ['zval**'], // zval* // 's', 'p', 'C' handled separately -); +]; /** reports an error, according to its level */ function error($str, $level = 0) @@ -118,7 +117,7 @@ function get_vars($txt) } /** run diagnostic checks against one var. */ -function check_param($db, $idx, $exp, $optional, $allow_uninit = false) +function check_param($db, $idx, string|array $exp, bool $optional, bool $allow_uninit = false) { global $error_few_vars_given; @@ -132,8 +131,19 @@ function check_param($db, $idx, $exp, $optional, $allow_uninit = false) return; } - if ($db[$idx][1] != $exp) { - error("{$db[$idx][0]}: expected '$exp' but got '{$db[$idx][1]}' [".($idx+1).']'); + $c_type = $db[$idx][1]; + if (is_array($exp)) { + if (!in_array($c_type, $exp)) { + $str = "{$db[$idx][0]}: expected "; + foreach ($exp as $expected_type) { + $str .= "'$expected_type' or "; + } + rtrim($str, 'or '); + $str .= "but got '{$c_type}' [".($idx+1).']'; + error($str); + } + } elseif ($c_type != $exp) { + error("{$db[$idx][0]}: expected '$exp' but got '{$c_type}' [".($idx+1).']'); } if (!$optional && $db[$idx][2]) { @@ -179,10 +189,8 @@ function get_params($vars, $str) /** run tests on a function. the code is passed in $txt */ function check_function($name, $txt, $offset) { - global $API_params; - $regex = '/ - (?: zend_parse_parameters(?:_throw)? \s*\([^,]+ + (?: zend_parse_parameters \s*\([^,]+ | zend_parse_(?:parameters_ex|method_parameters) \s*\([^,]+,[^,]+ | zend_parse_method_parameters_ex \s*\([^,]+,[^,]+,[^,+] ) @@ -222,14 +230,14 @@ function check_function($name, $txt, $offset) // separate_zval_if_not_ref case '/': - if (in_array($last_char, array('l', 'L', 'd', 'b'))) { + if (in_array($last_char, array('l', 'd', 'b'))) { error("the '/' specifier should not be applied to '$last_char'"); } break; // nullable arguments case '!': - if (in_array($last_char, array('l', 'L', 'd', 'b'))) { + if (in_array($last_char, array('l', 'd', 'b'))) { check_param($params, ++$j, 'bool*', $optional); } break; @@ -241,14 +249,14 @@ function check_function($name, $txt, $offset) error("A varargs specifier can only be used once. repeated char at column $i"); } else { check_param($params, ++$j, 'zval**', $optional); - check_param($params, ++$j, 'int*', $optional); + check_param($params, ++$j, 'uint32_t*', $optional); $varargs = true; } break; case 's': case 'p': - check_param($params, ++$j, 'char**', $optional, $allow_uninit=true); + check_param($params, ++$j, ['char**', 'unsigned char**'], $optional, $allow_uninit=true); check_param($params, ++$j, 'size_t*', $optional, $allow_uninit=true); if ($optional && !$params[$j-1][2] && !$params[$j][2] && $params[$j-1][0] !== '**dummy**' && $params[$j][0] !== '**dummy**') { @@ -261,17 +269,22 @@ function check_function($name, $txt, $offset) check_param($params, ++$j, 'zend_class_entry**', false); break; + case 'L': + case 'Z': + error("'$char' specifier is no longer allowed"); + break; + default: - if (!isset($API_params[$char])) { + if (!isset(ZPP_SPECIFIER_PARAMS[$char])) { error("unknown char ('$char') at column $i"); } // If an is_null flag is in use, only that flag is required to be // initialized $allow_uninit = $i+1 < $len && $spec[$i+1] === '!' - && in_array($char, array('l', 'L', 'd', 'b')); + && in_array($char, array('l', 'd', 'b')); - foreach ($API_params[$char] as $exp) { + foreach (ZPP_SPECIFIER_PARAMS[$char] as $exp) { check_param($params, ++$j, $exp, $optional, $allow_uninit); } }