Skip to content

Mark unterminated INIT_CALL and SEND_VAL instructions as NOP when removing unreachable basic blocks #5358

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
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
85 changes: 85 additions & 0 deletions ext/opcache/Optimizer/block_pass.c
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,89 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array
}
}

static uint32_t count_unterminated_init_calls(zend_op_array *op_array, zend_basic_block *b)
{
zend_op *op = op_array->opcodes + b->start;
zend_op *end = op + b->len;
uint32_t num_init_call = 0;
uint32_t num_do_call = 0;
for (; op < end; op++) {
if (op->opcode == ZEND_INIT_FCALL
|| op->opcode == ZEND_INIT_FCALL_BY_NAME
|| op->opcode == ZEND_INIT_NS_FCALL_BY_NAME
|| op->opcode == ZEND_INIT_DYNAMIC_CALL
|| op->opcode == ZEND_INIT_USER_CALL
|| op->opcode == ZEND_NEW
|| op->opcode == ZEND_INIT_METHOD_CALL
|| op->opcode == ZEND_INIT_STATIC_METHOD_CALL) {
num_init_call++;
}

if (op->opcode == ZEND_DO_FCALL
|| op->opcode == ZEND_DO_FCALL_BY_NAME
|| op->opcode == ZEND_DO_ICALL
|| op->opcode == ZEND_DO_UCALL) {
num_do_call++;
}
}

return num_do_call - num_init_call;
}

static void make_unterminated_init_call_nop(zend_cfg *cfg, zend_op_array *op_array, zend_basic_block *removed_b)
{
zend_basic_block *blocks = cfg->blocks;
uint32_t num_init_calls_to_delete = count_unterminated_init_calls(op_array, removed_b);
if (num_init_calls_to_delete <= 0) {
return;
}

zend_bool nesting = 0;
uint32_t num_init_calls_deleted = 0;
for (zend_basic_block *b = removed_b - 1; b >= blocks; b--) {
zend_op *op = op_array->opcodes + b->start + b->len;
zend_op *start = op - b->len;
for (; op >= start; op--) {
zend_bool is_init_call = op->opcode == ZEND_INIT_FCALL
|| op->opcode == ZEND_INIT_FCALL_BY_NAME
|| op->opcode == ZEND_INIT_NS_FCALL_BY_NAME
|| op->opcode == ZEND_INIT_DYNAMIC_CALL
|| op->opcode == ZEND_INIT_USER_CALL
|| op->opcode == ZEND_NEW
|| op->opcode == ZEND_INIT_METHOD_CALL
|| op->opcode == ZEND_INIT_STATIC_METHOD_CALL;

zend_bool is_do_call = op->opcode == ZEND_DO_FCALL
|| op->opcode == ZEND_DO_FCALL_BY_NAME
|| op->opcode == ZEND_DO_ICALL
|| op->opcode == ZEND_DO_UCALL;

zend_bool is_send_value = op->opcode == ZEND_SEND_VAL
|| op->opcode == ZEND_SEND_VAL_EX
|| op->opcode == ZEND_SEND_VAR
|| op->opcode == ZEND_SEND_VAR_EX
|| op->opcode == ZEND_SEND_VAR_NO_REF
|| op->opcode == ZEND_SEND_VAR_NO_REF_EX
|| op->opcode == ZEND_SEND_FUNC_ARG;

if (nesting == 0 && is_init_call) {
MAKE_NOP(op);
num_init_calls_deleted++;

if (num_init_calls_deleted >= num_init_calls_to_delete) {
return;
}
} else if (nesting == 0 && is_send_value) {
MAKE_NOP(op);
} else if (is_init_call) {
nesting--;
} else if (is_do_call) {
nesting++;
}
}
}
}

/* Rebuild plain (optimized) op_array from CFG */
static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array, zend_optimizer_ctx *ctx)
{
Expand Down Expand Up @@ -991,6 +1074,8 @@ static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array, zend_op
literal_dtor(&ZEND_OP2_LITERAL(op));
}
}

make_unterminated_init_call_nop(cfg, op_array, b);
}
}

Expand Down
28 changes: 28 additions & 0 deletions ext/opcache/tests/unterminated_init_call_bug.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
--TEST--
Mark unterminated INIT_CALL and SEND_VAL instructions as NOP when removing basic blocks
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.opt_debug_level=0x20000
--SKIPIF--
<?php require_once('skipif.inc'); ?>
--FILE--
<?php

var_dump(
var_dump('foo'),
exit()
);

--EXPECTF--
$_main:
; (lines=6, args=0, vars=0, tmps=1)
; (after optimizer)
; %s
0000 NOP
0001 INIT_FCALL 1 96 string("var_dump")
0002 SEND_VAL string("foo") 1
0003 V0 = DO_ICALL
0004 NOP
0005 EXIT
string(3) "foo"