Skip to content

Zend: Add zend_try_get_double - #23398

Merged
LamentXU123 merged 1 commit into
php:masterfrom
LamentXU123:trygetdouble
Sep 6, 2026
Merged

Zend: Add zend_try_get_double#23398
LamentXU123 merged 1 commit into
php:masterfrom
LamentXU123:trygetdouble

Conversation

@LamentXU123

@LamentXU123 LamentXU123 commented Aug 21, 2026

Copy link
Copy Markdown
Member

See php/php-tasks#32. Given now we have corresponding try functions to almost every zval_get_TYPE functions except double, it is reasonable to add zend_try_get_double. A quick search shows that there are 62 occasions of zend_get_double in the code base.

A real-world bug example is in #23384 (comment)_

cc @Girgias

I almost copy-paste the implementation of zend_try_get_long for this function.

@devnexen

Copy link
Copy Markdown
Member

that s a nice addition !

@LamentXU123
LamentXU123 marked this pull request as ready for review August 21, 2026 12:15
@LamentXU123
LamentXU123 requested a review from Girgias August 21, 2026 12:22
Comment thread Zend/zend_operators.c Outdated
ZEND_ASSERT(Z_TYPE(dst) == IS_DOUBLE);
return Z_DVAL(dst);
}
case IS_UNDEF:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't check this for zval_get_double, and I don't think we should do this here either. If you hand an UNDEF zval there are bigger issues at hand that need to be investigated.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I manually add this case :| But this makes debug easier so sure.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems the reason we do this in the other ones is for VM optimizations (which probably would be good to add a comment using the commit description as to why IS_UNDEF is checked there)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will also add a comment in code to make it clear.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't be reading an uninitialized typed property, that's a bug in the calling code.

@LamentXU123 LamentXU123 Aug 21, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay I take some hour to learn this stuff. AFAIK it is reasonable to remove IS_UNDEF. I don't know anything about VM optimizations, but I can tell that this:

case IS_UNDEF:
    *failed = true;
    return 0.0;

Is completely the wrong semantic. IS_UNDEF should not be considered as 0.0. This

a. makes debugging harder cuz this is hiding the bug and makes the code base even more unpredictable.
b. makes no sense because IS_UNDEF isn't any value and shouldn't be considered as 0 anyways.

Instead if we remove this logic, this falls into

default:
    ZEND_UNREACHABLE();

That immediately catch the bug. And is more nicer semantically because trying to turn it into a double value is indeed unreachable behavior.

I know we have *failed = true. But IMO this is more like indicating a error when turning the value. I think if you are trying to get a double from an UNDEF type, the problem is way more serious than that and errors need to be thrown here.

Also the bug you've mentioned make sense too. This isn't correct logic anyways.

Comment thread Zend/zend_operators.c Outdated
Comment thread ext/zend_test/tests/zval_try_get_double.phpt Outdated
Comment thread ext/zend_test/tests/zval_try_get_double.phpt Outdated
Comment thread Zend/zend_operators.c Outdated
}
/* }}} */

static zend_never_inline double ZEND_FASTCALL zval_try_get_double_func(const zval *op, bool *failed) /* {{{ */

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why never inline? Given this is called only from zval_try_get_double(), which cannot be inlined in other compilation units, this should very much be inlined.

Comment thread Zend/zend_operators.c Outdated
*failed = false;
return Z_DVAL_P(op);
}
return zval_try_get_double_func(op, failed);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern doesn't really make sense unless you move this function to the .h file so it can be inlined.

In that case, the zend_never_inline on zval_try_get_double_func() would make a bit more sense.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point indeed :)

@iluuu1994

Copy link
Copy Markdown
Member

I'd also ask why this is tested in zend_test instead of on actual extensions.

@LamentXU123

Copy link
Copy Markdown
Member Author

I'd also ask why this is tested in zend_test instead of on actual extensions.

Ah. Because I have yet to write a actual bug fix that use this function. So I only write test in zend_test as a new feature.

@iluuu1994

Copy link
Copy Markdown
Member

IMO we should not introduce functions that aren't made use of. If this is merged and branched into 8.6, we'll fix on an implementation we have no real proof is correct, and after that point changing it becomes a BC break for extensions.

@LamentXU123

LamentXU123 commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

Okay I add an actual fix in GD to use this internal function. So we might need reviews from David.

@@ -0,0 +1,99 @@
--TEST--

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not convinced gd is necessarily the best experiment terrain for this. e.g. standard pack() seems a better candidate.

@LamentXU123
LamentXU123 force-pushed the trygetdouble branch 2 times, most recently from 0d5e68e to c2ac548 Compare September 4, 2026 17:41
@LamentXU123
LamentXU123 requested a review from bukka as a code owner September 4, 2026 17:41
@LamentXU123

Copy link
Copy Markdown
Member Author

pack() now throws a TypeError when a value for the f, g, G, d, e, or E format code cannot be converted to float, instead of silently coercing it.

I guess this is a better way to use zend_try_get_double?

@devnexen

devnexen commented Sep 4, 2026

Copy link
Copy Markdown
Member

seems better but @iluuu1994 is right please address his comment.

@@ -0,0 +1,113 @@
--TEST--

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it surely deserves better coverage.

@LamentXU123

Copy link
Copy Markdown
Member Author

I've already Ilija's comment. Added more tests, I even test it's behavior in gmp :)

@devnexen

devnexen commented Sep 4, 2026

Copy link
Copy Markdown
Member

The declaration is still never inlined (zend_operators.h).

@devnexen devnexen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

except the nit, looking good but please wait another approval.

@iluuu1994

iluuu1994 commented Sep 4, 2026

Copy link
Copy Markdown
Member

I would agree to remove that. It's meaningless because zval_try_get_double() (or zval_try_get_double_func()) isn't called from Zend/zend_operators.c and so can't be inlined, and after LTO/PGO the hint is gone anyway.

Comment thread Zend/zend_operators.c Outdated
default: ZEND_UNREACHABLE();
}
}
/* }}} */

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also avoid vim folding on new functions

@devnexen

devnexen commented Sep 4, 2026

Copy link
Copy Markdown
Member

I may have approved a bit fast after all..

Comment thread ext/standard/pack.c Outdated
double d;
float v;
if (!php_pack_try_get_double(&argv[currentarg], currentarg + 2, &d)) {
zend_string_release(output);

@devnexen devnexen Sep 4, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems this cleanup is repetitive

Comment thread ext/standard/pack.c Outdated

*result = zval_try_get_double(value, &failed);
if (UNEXPECTED(failed)) {
if (!EG(exception)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not needed I think

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you may need to update php_pack too ?

@LamentXU123

Copy link
Copy Markdown
Member Author

Done. I also add more tests on error handling.

@LamentXU123

Copy link
Copy Markdown
Member Author

Okay. I am not familiar with the standard pack function and it is difficult for me to write bug fix for it. But I done it, so I am extremely not sure about this. This PR is now rather a bug fix to pack instead of barely adding a helper function to Zend.

Comment thread Zend/zend_operators.c Outdated
return 0.0;
}
}
return type == IS_LONG ? (double) lval : dval;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (type == IS_DOUBLE) {
   return dval;
}
if (UNEXPECTED(lval == 0)) {
   return zend_strtod(Z_STRVAL_P(op), NULL);
}
return (double) lval;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wdyt ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense to preserve "-0" here ig.

Comment thread Zend/zend_operators.c Outdated
return 0.0;
}
}
if (type == IS_DOUBLE) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realise ... this ... from 1100 to 1106 should be above the trailing data check above wdyt ?

Comment thread UPGRADING.INTERNALS
. Added zend_string_ends_with() and related variants.
. Added trait support for internal classes.
. Added do_php_cli().
. Added zval_try_get_double(), which converts a defined zval to a double and

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in the end, it should probably be reviewed.

@Girgias Girgias left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the changes to pack be split out from the PR? I'd rather have the API available for 8.6+ then the pack changes.

@LamentXU123

Copy link
Copy Markdown
Member Author

Okay I split the changes to pack out to #23590

@Girgias Girgias left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me :)

@LamentXU123
LamentXU123 merged commit 21034a8 into php:master Sep 6, 2026
18 checks passed
@LamentXU123

Copy link
Copy Markdown
Member Author

Thank you all! Very much appreciated.

@LamentXU123
LamentXU123 deleted the trygetdouble branch September 6, 2026 16:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants