Summary
In an AOT binary, converting an integer to a string consults the resource registry. If the
integer's value happens to equal a live resource handle id, it renders as Resource id #N instead
of the number.
This is the same root design flaw as #23483 — resource handles are stored as native longs and
php-types has no resource type, so Type::TYPE_LONG covers both — but in the string-conversion
path rather than inc/dec.
Reproducer
<?php
$fh = fopen('php://memory', 'r+');
$fh2 = fopen('php://memory', 'r+');
$a = 1; ++$a; // $a === 2, and handle #2 is live
$b = 2; ++$b;
$c = 3; ++$c;
$d = 4; --$d;
echo "$a $b $c $d\n";
zend: 2 3 4 3
aot : Resource id #2 3 4 3
Only $a is affected, because only its value (2) collides with a live handle id. The other three
render correctly. Opening a different number of resources moves which integer is corrupted, which
is the tell.
Note the value is arithmetically correct — 2 — it is only the rendering that treats it as a
resource. Any int-to-string conversion is presumably affected: interpolation, echo of a
concatenation, (string) casts, strval(), array keys printed by print_r.
Second defect in the same program
The same file also mis-evaluates a plain decrement, and then truncates:
$n = 0; $n--; $n--;
echo $n, "\n"; // zend: -2 aot: 0
After printing 0 the program produces no further output — the trailing fread/echo "done"
lines never run. Filed together because they share a reproducer; they may well be separate causes.
Context
Found by test/differential/cases/g07_incdec_resource_provenance.php, which was written for #23483
specifically to test integers colliding with plausible handle ids while resources are open. It
passes under VM and fails under AOT.
Not a regression from #23483's fix — measured. With lib/ at 544d1dca9 (immediately before that
change) the same program dies with rc=134/SIGABRT and no output at all; with the fix it gets
far enough to produce the wrong output above. So this is pre-existing breakage that the fix
un-masked.
Environment: php-compiler:22.04-dev, PHP 8.2.32, LLVM 9, php bin/compile.php -o <bin> <file>.
Summary
In an AOT binary, converting an integer to a string consults the resource registry. If the
integer's value happens to equal a live resource handle id, it renders as
Resource id #Ninsteadof the number.
This is the same root design flaw as #23483 — resource handles are stored as native longs and
php-types has no resource type, so
Type::TYPE_LONGcovers both — but in the string-conversionpath rather than inc/dec.
Reproducer
Only
$ais affected, because only its value (2) collides with a live handle id. The other threerender correctly. Opening a different number of resources moves which integer is corrupted, which
is the tell.
Note the value is arithmetically correct —
2— it is only the rendering that treats it as aresource. Any
int-to-stringconversion is presumably affected: interpolation,echoof aconcatenation,
(string)casts,strval(), array keys printed byprint_r.Second defect in the same program
The same file also mis-evaluates a plain decrement, and then truncates:
After printing
0the program produces no further output — the trailingfread/echo "done"lines never run. Filed together because they share a reproducer; they may well be separate causes.
Context
Found by
test/differential/cases/g07_incdec_resource_provenance.php, which was written for #23483specifically to test integers colliding with plausible handle ids while resources are open. It
passes under VM and fails under AOT.
Not a regression from #23483's fix — measured. With
lib/at544d1dca9(immediately before thatchange) the same program dies with
rc=134/SIGABRT and no output at all; with the fix it getsfar enough to produce the wrong output above. So this is pre-existing breakage that the fix
un-masked.
Environment:
php-compiler:22.04-dev, PHP 8.2.32, LLVM 9,php bin/compile.php -o <bin> <file>.