diff --git a/UPGRADING b/UPGRADING index 765b8c36a4ac8..4cc79ebd33042 100644 --- a/UPGRADING +++ b/UPGRADING @@ -103,6 +103,8 @@ PHP 8.6 UPGRADE NOTES . `printf()` using only `%s` and `%d` will be compiled into the equivalent string interpolation, avoiding the overhead of a function call and repeatedly parsing the format string. + . Arguments are now passed more efficiently to known constructors (e.g. when + using new self()). - JSON: . Improve performance of encoding arrays and objects. diff --git a/Zend/tests/oss_fuzz_454273637.phpt b/Zend/tests/oss_fuzz_454273637.phpt new file mode 100644 index 0000000000000..fbcdb57ed2a16 --- /dev/null +++ b/Zend/tests/oss_fuzz_454273637.phpt @@ -0,0 +1,8 @@ +--TEST-- +OSS-Fuzz #454273637 (UAF with printf optimization and const output) +--FILE-- + +--EXPECT-- +% diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 97e36d6b2498a..4d030991b5297 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -5009,9 +5009,17 @@ static zend_result zend_compile_func_printf(znode *result, zend_ast_list *args) * pass in the Zend Optimizer if the result of the printf() is in fact * unused */ znode copy; - zend_emit_op_tmp(©, ZEND_COPY_TMP, &rope_result, NULL); - zend_emit_op(NULL, ZEND_ECHO, &rope_result, NULL); - zend_emit_op_tmp(result, ZEND_STRLEN, ©, NULL); + if (rope_result.op_type != IS_CONST) { + /* Note: ZEND_COPY_TMP is only valid for TMPVAR. */ + ZEND_ASSERT(rope_result.op_type == IS_TMP_VAR); + zend_emit_op_tmp(©, ZEND_COPY_TMP, &rope_result, NULL); + zend_emit_op(NULL, ZEND_ECHO, &rope_result, NULL); + zend_emit_op_tmp(result, ZEND_STRLEN, ©, NULL); + } else { + zend_emit_op(NULL, ZEND_ECHO, &rope_result, NULL); + result->op_type = IS_CONST; + ZVAL_LONG(&result->u.constant, Z_STRLEN(rope_result.u.constant)); + } return SUCCESS; } @@ -5368,17 +5376,27 @@ static bool zend_is_constructor(zend_string *name) /* {{{ */ } /* }}} */ -static zend_function *zend_get_compatible_func_or_null(zend_class_entry *ce, zend_string *lcname) /* {{{ */ +static bool is_func_accessible(const zend_function *fbc) { - zend_function *fbc = zend_hash_find_ptr(&ce->function_table, lcname); - if (!fbc || (fbc->common.fn_flags & ZEND_ACC_PUBLIC) || ce == CG(active_class_entry)) { - return fbc; + if ((fbc->common.fn_flags & ZEND_ACC_PUBLIC) || fbc->common.scope == CG(active_class_entry)) { + return true; } if (!(fbc->common.fn_flags & ZEND_ACC_PRIVATE) && (fbc->common.scope->ce_flags & ZEND_ACC_LINKED) && (!CG(active_class_entry) || (CG(active_class_entry)->ce_flags & ZEND_ACC_LINKED)) && zend_check_protected(zend_get_function_root_class(fbc), CG(active_class_entry))) { + return true; + } + + return false; +} + +static zend_function *zend_get_compatible_func_or_null(zend_class_entry *ce, zend_string *lcname) /* {{{ */ +{ + zend_function *fbc = zend_hash_find_ptr(&ce->function_table, lcname); + + if (!fbc || is_func_accessible(fbc)) { return fbc; } @@ -5481,16 +5499,40 @@ static void zend_compile_new(znode *result, zend_ast *ast) /* {{{ */ opline = zend_emit_op(result, ZEND_NEW, NULL, NULL); - if (class_node.op_type == IS_CONST) { - opline->op1_type = IS_CONST; - opline->op1.constant = zend_add_class_name_literal( - Z_STR(class_node.u.constant)); + zend_set_class_name_op1(opline, &class_node); + + if (opline->op1_type == IS_CONST) { opline->op2.num = zend_alloc_cache_slot(); - } else { - SET_NODE(opline->op1, &class_node); } - zend_compile_call_common(&ctor_result, args_ast, NULL, ast->lineno); + zend_class_entry *ce = NULL; + if (opline->op1_type == IS_CONST) { + zend_string *lcname = Z_STR_P(CT_CONSTANT(opline->op1) + 1); + ce = zend_hash_find_ptr(CG(class_table), lcname); + if (ce) { + if (zend_compile_ignore_class(ce, CG(active_op_array)->filename)) { + ce = NULL; + } + } else if (CG(active_class_entry) + && zend_string_equals_ci(CG(active_class_entry)->name, lcname)) { + ce = CG(active_class_entry); + } + } else if (opline->op1_type == IS_UNUSED + && (opline->op1.num & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_SELF + && zend_is_scope_known()) { + ce = CG(active_class_entry); + } + + + zend_function *fbc = NULL; + if (ce + && ce->default_object_handlers->get_constructor == zend_std_get_constructor + && ce->constructor + && is_func_accessible(ce->constructor)) { + fbc = ce->constructor; + } + + zend_compile_call_common(&ctor_result, args_ast, fbc, ast->lineno); zend_do_free(&ctor_result); } /* }}} */ diff --git a/ext/opcache/tests/named_parameter_new.phpt b/ext/opcache/tests/named_parameter_new.phpt new file mode 100644 index 0000000000000..78bce98897702 --- /dev/null +++ b/ext/opcache/tests/named_parameter_new.phpt @@ -0,0 +1,72 @@ +--TEST-- +Named Parameters are optimized for known constructors +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.opt_debug_level=0x20000 +--SKIPIF-- + +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECTF-- +$_main: + ; (lines=4, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %s +0000 INIT_STATIC_METHOD_CALL 1 string("MyClass") string("new") +0001 SEND_VAL int(1) 1 +0002 DO_UCALL +0003 RETURN int(1) + +MyClass::__construct: + ; (lines=7, args=2, vars=2, tmps=0) + ; (after optimizer) + ; %s +0000 CV0($foo) = RECV 1 +0001 CV1($bar) = RECV_INIT 2 int(0) +0002 ASSIGN_OBJ THIS string("foo") +0003 OP_DATA CV0($foo) +0004 ASSIGN_OBJ THIS string("bar") +0005 OP_DATA CV1($bar) +0006 RETURN null + +MyClass::new: + ; (lines=10, args=1, vars=2, tmps=1) + ; (after optimizer) + ; %s +0000 CV0($bar) = RECV 1 +0001 V2 = NEW 1 string("Random\\Engine\\Xoshiro256StarStar") +0002 SEND_VAL int(123) 1 +0003 DO_FCALL +0004 CV1($engine) = QM_ASSIGN V2 +0005 V2 = NEW 2 (self) (exception) +0006 SEND_VAR CV1($engine) 1 +0007 SEND_VAR CV0($bar) 2 +0008 DO_FCALL +0009 RETURN V2 +LIVE RANGES: + 2: 0002 - 0004 (new) + 2: 0006 - 0009 (new) diff --git a/ext/opcache/tests/opt/gh18107_1.phpt b/ext/opcache/tests/opt/gh18107_1.phpt index fea270a4d5a08..062ccd464559b 100644 --- a/ext/opcache/tests/opt/gh18107_1.phpt +++ b/ext/opcache/tests/opt/gh18107_1.phpt @@ -30,12 +30,12 @@ $_main: 0000 T1 = ISSET_ISEMPTY_CV (isset) CV0($badvar) 0001 JMPNZ T1 0006 0002 V3 = NEW 1 string("Exception") -0003 SEND_VAL_EX string("Should happen") 1 +0003 SEND_VAL%S string("Should happen") 1 0004 DO_FCALL 0005 THROW V3 0006 JMP 0006 0007 V6 = NEW 1 string("Exception") -0008 SEND_VAL_EX string("Should not happen") 1 +0008 SEND_VAL%S string("Should not happen") 1 0009 DO_FCALL 0010 THROW V6 0011 FAST_RET T5 diff --git a/ext/opcache/tests/opt/gh18107_2.phpt b/ext/opcache/tests/opt/gh18107_2.phpt index aaf58b4f820de..bd34a54edf6e6 100644 --- a/ext/opcache/tests/opt/gh18107_2.phpt +++ b/ext/opcache/tests/opt/gh18107_2.phpt @@ -33,7 +33,7 @@ $_main: 0000 T2 = ISSET_ISEMPTY_CV (isset) CV0($badvar) 0001 JMPNZ T2 0008 0002 V4 = NEW 1 string("Exception") -0003 SEND_VAL_EX string("Should happen") 1 +0003 SEND_VAL%S string("Should happen") 1 0004 DO_FCALL 0005 THROW V4 0006 CV1($e) = CATCH string("Throwable") @@ -41,7 +41,7 @@ $_main: 0008 T6 = FAST_CALL 0010 0009 JMP 0015 0010 V7 = NEW 1 string("Exception") -0011 SEND_VAL_EX string("Should not happen") 1 +0011 SEND_VAL%S string("Should not happen") 1 0012 DO_FCALL 0013 THROW V7 0014 FAST_RET T6