Skip to content

ColumnVector::modMatrix() and divideMatrix() abort the process #3

Description

@Jeckerson

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions