Skip to content

round() with a RoundingMode enum aborts the compiled binary with stack smashing #29

Description

@Giandonn

Summary

genRound sends the third argument of round() through convertIntExpr.
Since PHP 8.4 that parameter is a RoundingMode enum, and php::fn::round()
declares the mode as an Int, so the enum is put through an object-to-int
conversion.

Reproducer

<?php
function main(): void
{
    echo "round(2.5,0,HalfEven): ", round(2.5, 0, RoundingMode::HalfEven), "\n";
}

Expected (PHP 8.5.4)

round(2.5,0,HalfEven): 2

Actual (compiled binary)

*** stack smashing detected ***: terminated
Aborted

What is actually going on

Two separate problems stack on this one line, and they are worth separating:

  1. phpx returns an unevaluated class constant for an enum case of an
    internal class, so RoundingMode::HalfEven is not a usable value at all.
    That is the crash. Reported as constant(zend_class_entry*, name) returns an unevaluated class constant phpx#97, with a fix in fix: evaluate class constants before returning them phpx#98.

  2. This repo converts that argument to an int. With the phpx side fixed, the
    compiled binary stops crashing but still disagrees with PHP:

    Warning: Object of class RoundingMode could not be converted to int
    3
    

    The conversion yields 1, which is PHP_ROUND_HALF_UP, so banker's rounding
    silently becomes half away from zero.

Measured on a compiled binary, PHP 8.5.4 ZTS + embed, GCC 15, Linux x64:

phpx genRound round(2.5, 0, RoundingMode::HalfEven)
master master stack smashing abort
fixed master warning, then 3
master fixed clean TypeError, no memory corruption
fixed fixed 2, same as PHP

Code that spells out HalfEven is usually money code, where half-up is the
exact behaviour it was avoiding.

Suggested fix

Let a mode that is not statically an int fall through to the dynamic path, which
passes the enum to the runtime function unchanged. The legacy PHP_ROUND_*
integer constants keep the native call. PR follows.

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