========================================
1. Backward Incompatible Changes
========================================
+
- Core:
. 'void' can no longer be used as the name of a class, interface, or trait.
This applies to declarations, class_alias() and use statements.
(RFC: https://wiki.php.net/rfc/invalid_strings_in_arithmetic)
. The ASCII 0x7F Delete control character is no longer permitted in unquoted
identifiers in source code.
+ . The following functions may no longer be called dynamically using $func(),
+ call_user_func(), array_map() or similar:
+ . extract()
+ . compact()
+ . get_defined_vars()
+ . func_get_args()
+ . func_get_arg()
+ . func_num_args()
+ . parse_str() with one argument
+ . mb_parse_str() with one argument
+ . assert() with a string argument
+ (RFC: https://wiki.php.net/rfc/forbid_dynamic_scope_introspection)
- JSON:
. When calling json_encode with JSON_UNESCAPED_UNICODE option, U+2028 and
--- /dev/null
+--TEST--
+Bug #72107: Segfault when using func_get_args as error handler
+--FILE--
+<?php
+set_error_handler('func_get_args');
+function test($a) {
+ echo $undef;
+}
+test(1);
+?>
+--EXPECTF--
+Warning: Cannot call func_get_args() dynamically in %s on line %d
+
+Notice: Undefined variable: undef in %s on line %d
--- /dev/null
+--TEST--
+Dynamic calls to scope introspection functions are forbidden
+--FILE--
+<?php
+
+function test_calls($func) {
+ $i = 1;
+
+ array_map($func, [['i' => new stdClass]]);
+ var_dump($i);
+
+ $func(['i' => new stdClass]);
+ var_dump($i);
+
+ call_user_func($func, ['i' => new stdClass]);
+ var_dump($i);
+}
+test_calls('extract');
+
+?>
+--EXPECTF--
+Warning: Cannot call extract() dynamically in %s on line %d
+int(1)
+
+Warning: Cannot call extract() dynamically in %s on line %d
+int(1)
+
+Warning: Cannot call extract() dynamically in %s on line %d
+int(1)
--- /dev/null
+--TEST--
+Dynamic calls to scope introspection functions are forbidden (function variations)
+--FILE--
+<?php
+function test() {
+ $func = 'extract';
+ $func(['a' => 'b']);
+
+ $func = 'compact';
+ $func(['a']);
+
+ $func = 'parse_str';
+ $func('a=b');
+
+ $func = 'get_defined_vars';
+ $func();
+
+ $func = 'assert';
+ $func('1==2');
+
+ $func = 'func_get_args';
+ $func();
+
+ $func = 'func_get_arg';
+ $func(1);
+
+ $func = 'func_num_args';
+ $func();
+}
+test();
+
+?>
+--EXPECTF--
+Warning: Cannot call extract() dynamically in %s on line %d
+
+Warning: Cannot call compact() dynamically in %s on line %d
+
+Warning: Cannot call parse_str() with a single argument dynamically in %s on line %d
+
+Warning: Cannot call get_defined_vars() dynamically in %s on line %d
+
+Warning: Cannot call assert() with string argument dynamically in %s on line %d
+
+Warning: Cannot call func_get_args() dynamically in %s on line %d
+
+Warning: Cannot call func_get_arg() dynamically in %s on line %d
+
+Warning: Cannot call func_num_args() dynamically in %s on line %d
--- /dev/null
+--TEST--
+Dynamic calls to scope introspection functions are forbidden (misoptimization)
+--FILE--
+<?php
+
+function test() {
+ $i = 1;
+ array_map('extract', [['i' => new stdClass]]);
+ $i += 1;
+ var_dump($i);
+}
+test();
+
+?>
+--EXPECTF--
+Warning: Cannot call extract() dynamically in %s on line %d
+int(2)
ZEND_API void zend_detach_symbol_table(zend_execute_data *execute_data);
ZEND_API int zend_set_local_var(zend_string *name, zval *value, int force);
ZEND_API int zend_set_local_var_str(const char *name, size_t len, zval *value, int force);
+ZEND_API int zend_forbid_dynamic_call(const char *func_name);
ZEND_API zend_string *zend_find_alias_name(zend_class_entry *ce, zend_string *name);
ZEND_API zend_string *zend_resolve_method_name(zend_class_entry *ce, zend_function *f);
{
zend_execute_data *ex = EX(prev_execute_data);
- if (!(ZEND_CALL_INFO(ex) & ZEND_CALL_CODE)) {
- RETURN_LONG(ZEND_CALL_NUM_ARGS(ex));
- } else {
+ if (ZEND_CALL_INFO(ex) & ZEND_CALL_CODE) {
zend_error(E_WARNING, "func_num_args(): Called from the global scope - no function context");
RETURN_LONG(-1);
}
+
+ if (zend_forbid_dynamic_call("func_num_args()") == FAILURE) {
+ RETURN_LONG(-1);
+ }
+
+ RETURN_LONG(ZEND_CALL_NUM_ARGS(ex));
}
/* }}} */
RETURN_FALSE;
}
+ if (zend_forbid_dynamic_call("func_get_arg()") == FAILURE) {
+ RETURN_FALSE;
+ }
+
arg_count = ZEND_CALL_NUM_ARGS(ex);
if ((zend_ulong)requested_offset >= arg_count) {
RETURN_FALSE;
}
+ if (zend_forbid_dynamic_call("func_get_args()") == FAILURE) {
+ RETURN_FALSE;
+ }
+
arg_count = ZEND_CALL_NUM_ARGS(ex);
array_init_size(return_value, arg_count);
Returns an associative array of names and values of all currently defined variable names (variables in the current scope) */
ZEND_FUNCTION(get_defined_vars)
{
- zend_array *symbol_table = zend_rebuild_symbol_table();
+ zend_array *symbol_table;
+ if (zend_forbid_dynamic_call("get_defined_vars()") == FAILURE) {
+ return;
+ }
+ symbol_table = zend_rebuild_symbol_table();
if (UNEXPECTED(symbol_table == NULL)) {
return;
}
#define ZEND_CALL_RELEASE_THIS (1 << 6)
#define ZEND_CALL_ALLOCATED (1 << 7)
#define ZEND_CALL_GENERATOR (1 << 8)
+#define ZEND_CALL_DYNAMIC (1 << 9)
#define ZEND_CALL_INFO_SHIFT 16
init_func_run_time_cache(&fbc->op_array);
}
- return zend_vm_stack_push_call_frame(ZEND_CALL_NESTED_FUNCTION,
+ return zend_vm_stack_push_call_frame(ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_DYNAMIC,
fbc, num_args, called_scope, NULL);
}
/* }}} */
zend_function *fbc;
zend_class_entry *called_scope;
zend_object *object;
- uint32_t call_info = ZEND_CALL_NESTED_FUNCTION;
+ uint32_t call_info = ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_DYNAMIC;
if (EXPECTED(Z_OBJ_HANDLER_P(function, get_closure)) &&
EXPECTED(Z_OBJ_HANDLER_P(function, get_closure)(function, &called_scope, &fbc, &object) == SUCCESS)) {
zend_function *fbc;
zend_class_entry *called_scope;
zend_object *object;
- uint32_t call_info = ZEND_CALL_NESTED_FUNCTION;
+ uint32_t call_info = ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_DYNAMIC;
if (zend_hash_num_elements(function) == 2) {
zval *obj;
fci->object = (func->common.fn_flags & ZEND_ACC_STATIC) ?
NULL : fci_cache->object;
- call = zend_vm_stack_push_call_frame(ZEND_CALL_TOP_FUNCTION,
+ call = zend_vm_stack_push_call_frame(ZEND_CALL_TOP_FUNCTION | ZEND_CALL_DYNAMIC,
func, fci->param_count, fci_cache->called_scope, fci->object);
if (fci->object &&
(!EG(objects_store).object_buckets ||
}
/* }}} */
+ZEND_API int zend_forbid_dynamic_call(const char *func_name) /* {{{ */
+{
+ zend_execute_data *ex = EG(current_execute_data);
+ ZEND_ASSERT(ex != NULL && ex->func != NULL);
+
+ if (ZEND_CALL_INFO(ex) & ZEND_CALL_DYNAMIC) {
+ zend_error(E_WARNING, "Cannot call %s dynamically", func_name);
+ return FAILURE;
+ }
+
+ return SUCCESS;
+}
+/* }}} */
+
/*
* Local variables:
* tab-width: 4
zend_class_entry *called_scope;
zend_object *object;
zend_execute_data *call;
- uint32_t call_info = ZEND_CALL_NESTED_FUNCTION;
+ uint32_t call_info = ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_DYNAMIC;
SAVE_OPLINE();
function_name = GET_OP2_ZVAL_PTR(BP_VAR_R);
zend_class_entry *called_scope;
zend_object *object;
zend_execute_data *call;
- uint32_t call_info = ZEND_CALL_NESTED_FUNCTION;
+ uint32_t call_info = ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_DYNAMIC;
SAVE_OPLINE();
function_name = EX_CONSTANT(opline->op2);
zend_class_entry *called_scope;
zend_object *object;
zend_execute_data *call;
- uint32_t call_info = ZEND_CALL_NESTED_FUNCTION;
+ uint32_t call_info = ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_DYNAMIC;
SAVE_OPLINE();
function_name = _get_zval_ptr_cv_BP_VAR_R(execute_data, opline->op2.var);
zend_class_entry *called_scope;
zend_object *object;
zend_execute_data *call;
- uint32_t call_info = ZEND_CALL_NESTED_FUNCTION;
+ uint32_t call_info = ZEND_CALL_NESTED_FUNCTION | ZEND_CALL_DYNAMIC;
SAVE_OPLINE();
function_name = _get_zval_ptr_var(opline->op2.var, execute_data, &free_op2);
detected = _php_mb_encoding_handler_ex(&info, track_vars_array, encstr);
} else {
zval tmp;
- zend_array *symbol_table = zend_rebuild_symbol_table();
+ zend_array *symbol_table;
+ if (zend_forbid_dynamic_call("mb_parse_str() with a single argument") == FAILURE) {
+ efree(encstr);
+ return;
+ }
+ symbol_table = zend_rebuild_symbol_table();
ZVAL_ARR(&tmp, symbol_table);
detected = _php_mb_encoding_handler_ex(&info, &tmp, encstr);
}
}
}
+ if (zend_forbid_dynamic_call("extract()") == FAILURE) {
+ return;
+ }
+
symbol_table = zend_rebuild_symbol_table();
#if 0
if (!symbol_table) {
return;
}
- symbol_table = zend_rebuild_symbol_table();
+ if (zend_forbid_dynamic_call("compact()") == FAILURE) {
+ return;
+ }
+ symbol_table = zend_rebuild_symbol_table();
if (UNEXPECTED(symbol_table == NULL)) {
return;
}
zval retval;
int old_error_reporting = 0; /* shut up gcc! */
+ if (zend_forbid_dynamic_call("assert() with string argument") == FAILURE) {
+ RETURN_FALSE;
+ }
+
myeval = Z_STRVAL_P(assertion);
if (ASSERTG(quiet_eval)) {
if (arrayArg == NULL) {
zval tmp;
- zend_array *symbol_table = zend_rebuild_symbol_table();
+ zend_array *symbol_table;
+ if (zend_forbid_dynamic_call("parse_str() with a single argument") == FAILURE) {
+ efree(res);
+ return;
+ }
+ symbol_table = zend_rebuild_symbol_table();
ZVAL_ARR(&tmp, symbol_table);
sapi_module.treat_data(PARSE_STRING, res, &tmp);
} else {