Skip to content
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
2 changes: 2 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -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.
8 changes: 8 additions & 0 deletions Zend/tests/oss_fuzz_454273637.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
--TEST--
OSS-Fuzz #454273637 (UAF with printf optimization and const output)
--FILE--
<?php
printf('%%');
?>
--EXPECT--
%
70 changes: 56 additions & 14 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -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(&copy, ZEND_COPY_TMP, &rope_result, NULL);
zend_emit_op(NULL, ZEND_ECHO, &rope_result, NULL);
zend_emit_op_tmp(result, ZEND_STRLEN, &copy, 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(&copy, ZEND_COPY_TMP, &rope_result, NULL);
zend_emit_op(NULL, ZEND_ECHO, &rope_result, NULL);
zend_emit_op_tmp(result, ZEND_STRLEN, &copy, 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;
}
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);
}
/* }}} */
Expand Down
72 changes: 72 additions & 0 deletions ext/opcache/tests/named_parameter_new.phpt
Original file line number Diff line number Diff line change
@@ -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--
<?php
if(substr(PHP_OS, 0, 3) == 'WIN') die("skip on windows: Internal classes cannot be optimized");
?>
--EXTENSIONS--
opcache
--FILE--
<?php

final class MyClass
{
private function __construct(
private \Random\Engine $foo,
private int $bar = 0,
) {}

public static function new(int $bar): self
{
$engine = new \Random\Engine\Xoshiro256StarStar(seed: 123);
return new self(foo: $engine, bar: $bar);
}
}

MyClass::new(bar: 1);

?>
--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)
4 changes: 2 additions & 2 deletions ext/opcache/tests/opt/gh18107_1.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions ext/opcache/tests/opt/gh18107_2.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@ $_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")
0007 ECHO string("foo")
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
Expand Down