Skip to content

Commit a04d871

Browse files
committed
Fix GH-13817: Segmentation fault for enabled observers after pass 4
Instead of fixing up temporaries count in between observer steps, just take ZEND_ACC_DONE_PASS_TWO into account during stack_size calculation. Introducing zend_vm_calc_ct_used_stack for that use case. This should be much less susceptible to forgetting to handle the ZEND_OBSERVER_ENABLED temporary explicitly.
1 parent b3e26c3 commit a04d871

File tree

6 files changed

+67
-12
lines changed

6 files changed

+67
-12
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ PHP NEWS
2929
- Opcache:
3030
. Fixed incorrect assumptions across compilation units for static calls.
3131
(ilutov)
32+
. Fixed bug GH-13817 (Segmentation fault for enabled observers after pass 4).
33+
(Bob)
3234

3335
- OpenSSL:
3436
. Fixed bug GH-10495 (feof on OpenSSL stream hangs indefinitely).

Zend/Optimizer/optimize_func_calls.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,15 +195,15 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
195195
/* nothing to do */
196196
} else if (fcall->opcode == ZEND_INIT_FCALL_BY_NAME) {
197197
fcall->opcode = ZEND_INIT_FCALL;
198-
fcall->op1.num = zend_vm_calc_used_stack(fcall->extended_value, call_stack[call].func);
198+
fcall->op1.num = zend_vm_calc_ct_used_stack(fcall->extended_value, call_stack[call].func);
199199
literal_dtor(&ZEND_OP2_LITERAL(fcall));
200200
fcall->op2.constant = fcall->op2.constant + 1;
201201
if (opline->opcode != ZEND_CALLABLE_CONVERT) {
202202
opline->opcode = zend_get_call_op(fcall, call_stack[call].func);
203203
}
204204
} else if (fcall->opcode == ZEND_INIT_NS_FCALL_BY_NAME) {
205205
fcall->opcode = ZEND_INIT_FCALL;
206-
fcall->op1.num = zend_vm_calc_used_stack(fcall->extended_value, call_stack[call].func);
206+
fcall->op1.num = zend_vm_calc_ct_used_stack(fcall->extended_value, call_stack[call].func);
207207
literal_dtor(&op_array->literals[fcall->op2.constant]);
208208
literal_dtor(&op_array->literals[fcall->op2.constant + 2]);
209209
fcall->op2.constant = fcall->op2.constant + 1;

Zend/Optimizer/zend_optimizer.c

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1238,6 +1238,8 @@ static void zend_redo_pass_two_ex(zend_op_array *op_array, zend_ssa *ssa)
12381238
}
12391239
#endif
12401240

1241+
op_array->T += ZEND_OBSERVER_ENABLED; // reserve last temporary for observers if enabled
1242+
12411243
opline = op_array->opcodes;
12421244
end = opline + op_array->last;
12431245
while (opline < end) {
@@ -1362,7 +1364,7 @@ static void zend_adjust_fcall_stack_size(zend_op_array *op_array, zend_optimizer
13621364
&ctx->script->function_table,
13631365
Z_STR_P(RT_CONSTANT(opline, opline->op2)));
13641366
if (func) {
1365-
opline->op1.num = zend_vm_calc_used_stack(opline->extended_value, func);
1367+
opline->op1.num = zend_vm_calc_ct_used_stack(opline->extended_value, func);
13661368
}
13671369
}
13681370
opline++;
@@ -1381,7 +1383,7 @@ static void zend_adjust_fcall_stack_size_graph(zend_op_array *op_array)
13811383

13821384
if (opline && call_info->callee_func && opline->opcode == ZEND_INIT_FCALL) {
13831385
ZEND_ASSERT(!call_info->is_prototype);
1384-
opline->op1.num = zend_vm_calc_used_stack(opline->extended_value, call_info->callee_func);
1386+
opline->op1.num = zend_vm_calc_ct_used_stack(opline->extended_value, call_info->callee_func);
13851387
}
13861388
call_info = call_info->next_callee;
13871389
}
@@ -1557,12 +1559,6 @@ ZEND_API void zend_optimize_script(zend_script *script, zend_long optimization_l
15571559
}
15581560
}
15591561

1560-
if (ZEND_OBSERVER_ENABLED) {
1561-
for (i = 0; i < call_graph.op_arrays_count; i++) {
1562-
++call_graph.op_arrays[i]->T; // ensure accurate temporary count for stack size precalculation
1563-
}
1564-
}
1565-
15661562
if (ZEND_OPTIMIZER_PASS_12 & optimization_level) {
15671563
for (i = 0; i < call_graph.op_arrays_count; i++) {
15681564
zend_adjust_fcall_stack_size_graph(call_graph.op_arrays[i]);
@@ -1578,8 +1574,6 @@ ZEND_API void zend_optimize_script(zend_script *script, zend_long optimization_l
15781574
zend_recalc_live_ranges(op_array, needs_live_range);
15791575
}
15801576
} else {
1581-
op_array->T -= ZEND_OBSERVER_ENABLED; // redo_pass_two will re-increment it
1582-
15831577
zend_redo_pass_two(op_array);
15841578
if (op_array->live_range) {
15851579
zend_recalc_live_ranges(op_array, NULL);

Zend/zend_execute.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,11 @@ ZEND_API void* zend_vm_stack_extend(size_t size)
226226
return ptr;
227227
}
228228

229+
ZEND_API uint32_t zend_vm_calc_ct_used_stack(uint32_t num_args, zend_function *func)
230+
{
231+
return zend_vm_calc_used_stack(num_args, func) + ((func->common.fn_flags & ZEND_ACC_DONE_PASS_TWO) == 0 && ZEND_USER_CODE(func->type) ? ZEND_OBSERVER_ENABLED : 0) * sizeof(zval);
232+
}
233+
229234
ZEND_API zval* zend_get_compiled_variable_value(const zend_execute_data *execute_data, uint32_t var)
230235
{
231236
return EX_VAR(var);

Zend/zend_execute.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,9 @@ static zend_always_inline uint32_t zend_vm_calc_used_stack(uint32_t num_args, ze
255255
return used_stack * sizeof(zval);
256256
}
257257

258+
// Handle a possibly currently not applied pass_two
259+
ZEND_API uint32_t zend_vm_calc_ct_used_stack(uint32_t num_args, zend_function *func);
260+
258261
static zend_always_inline zend_execute_data *zend_vm_stack_push_call_frame(uint32_t call_info, zend_function *func, uint32_t num_args, void *object_or_called_scope)
259262
{
260263
uint32_t used_stack = zend_vm_calc_used_stack(num_args, func);

ext/opcache/tests/gh13817.phpt

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
--TEST--
2+
GH-13712 (Segmentation fault for enabled observers after pass 4)
3+
--EXTENSIONS--
4+
opcache
5+
zend_test
6+
--INI--
7+
zend_test.observer.enabled=1
8+
zend_test.observer.show_output=1
9+
zend_test.observer.observe_all=1
10+
opcache.enable=1
11+
opcache.enable_cli=1
12+
opcache.optimization_level=0x4069
13+
--FILE--
14+
<?php
15+
16+
function inner() {
17+
echo "Ok\n";
18+
}
19+
20+
function foo() {
21+
// If stack size is wrong, inner() will corrupt the previous observed frame
22+
inner();
23+
}
24+
25+
// After foo() def so that we land here, with step_two undone for foo() first
26+
function outer() {
27+
// Pass 15 does constant string propagation, which gives a ZEND_INIT_DYNAMIC_FCALL on a const which Pass 4 will optimize
28+
// Pass 4 must calc the right stack size here
29+
(NAME)();
30+
}
31+
32+
const NAME = "foo";
33+
34+
outer();
35+
36+
?>
37+
--EXPECTF--
38+
<!-- init '%s' -->
39+
<file '%s'>
40+
<!-- init outer() -->
41+
<outer>
42+
<!-- init foo() -->
43+
<foo>
44+
<!-- init inner() -->
45+
<inner>
46+
Ok
47+
</inner>
48+
</foo>
49+
</outer>
50+
</file '%s'>
51+

0 commit comments

Comments
 (0)