Summary
When an arithmetic exception is raised part-way through one of ColumnVector's *Matrix
broadcasts, the process dies with SIGABRT instead of throwing. It cannot be caught, so a single
piece of unlucky user data takes down the whole PHP process — under php-fpm, the whole worker.
Affects master (b197cbe).
Reproduction
<?php
// 0.5 truncates to integer 0, so the modulo raises DivisionByZeroError.
Tensor\ColumnVector::quick([1.5, 2.5])
->modMatrix(Tensor\Matrix::quick([[0.5, 2.0], [0.5, 2.0]]));
Variable 0x7ffe81a04490 is already observed
#0 zephir_print_backtrace
#1 zephir_do_memory_observe
#2 zim_Tensor_ColumnVector_modMatrix
...
Aborted (core dumped) # exit 134
divideMatrix() by an exact 0.0 aborts identically. powMatrix() does not, because it raises
nothing. The trigger is the exception, not the operator.
Not a general problem with these operators
The same arithmetic through any other spelling behaves correctly and throws
DivisionByZeroError:
| call |
result |
ColumnVector::modMatrix(...) |
SIGABRT, exit 134 |
ColumnVector::mod(Matrix) |
SIGABRT, exit 134 (dispatches to the above) |
ColumnVector::modVector(...) |
throws |
ColumnVector::modScalar(...) |
throws |
Vector::modMatrix(...) |
throws |
Matrix::modColumnVector(...) |
throws |
Cause
ColumnVector's twelve *Matrix overrides (tensor/columnvector.zep:87-497) are the only
element-wise family still written as nested Zephir loops; every other family routes through a C
handler in ext/include/. The generated code observes a temporary inside the loop body. When the
operator raises, the exception propagates without the memory frame being unwound, and the next
ZEPHIR_OBS_VAR on the same slot trips the kernel's own consistency check, which calls abort().
That is also why only this family is affected: the C handlers never leave a Zephir frame in a
half-finished state, because they do not open one per iteration.
Suggested fix
Give these twelve methods C handlers, as every other element-wise family already has. That removes
the cause rather than patching around it, and it is worth doing on its own merits — they are
currently the only broadcast family running as interpreted Zephir.
A narrower fix is to hoist the observed temporary out of the loop in columnvector.zep so the
frame stays consistent across an early exit.
Impact
ColumnVector::divideMatrix() is the more serious of the two: a zero anywhere in the operand
matrix is enough, and zeros in real data are not exotic. There is no way for a caller to defend
against it, because the failure is not an exception.
Summary
When an arithmetic exception is raised part-way through one of
ColumnVector's*Matrixbroadcasts, the process dies with SIGABRT instead of throwing. It cannot be caught, so a single
piece of unlucky user data takes down the whole PHP process — under php-fpm, the whole worker.
Affects
master(b197cbe).Reproduction
divideMatrix()by an exact0.0aborts identically.powMatrix()does not, because it raisesnothing. The trigger is the exception, not the operator.
Not a general problem with these operators
The same arithmetic through any other spelling behaves correctly and throws
DivisionByZeroError:ColumnVector::modMatrix(...)ColumnVector::mod(Matrix)ColumnVector::modVector(...)ColumnVector::modScalar(...)Vector::modMatrix(...)Matrix::modColumnVector(...)Cause
ColumnVector's twelve*Matrixoverrides (tensor/columnvector.zep:87-497) are the onlyelement-wise family still written as nested Zephir loops; every other family routes through a C
handler in
ext/include/. The generated code observes a temporary inside the loop body. When theoperator raises, the exception propagates without the memory frame being unwound, and the next
ZEPHIR_OBS_VARon the same slot trips the kernel's own consistency check, which callsabort().That is also why only this family is affected: the C handlers never leave a Zephir frame in a
half-finished state, because they do not open one per iteration.
Suggested fix
Give these twelve methods C handlers, as every other element-wise family already has. That removes
the cause rather than patching around it, and it is worth doing on its own merits — they are
currently the only broadcast family running as interpreted Zephir.
A narrower fix is to hoist the observed temporary out of the loop in
columnvector.zepso theframe stays consistent across an early exit.
Impact
ColumnVector::divideMatrix()is the more serious of the two: a zero anywhere in the operandmatrix is enough, and zeros in real data are not exotic. There is no way for a caller to defend
against it, because the failure is not an exception.