Skip to content

Fix GH-17144: type inference narrowing on ZEND_FETCH_DIM_W - #21674

Open
iliaal wants to merge 1 commit into
php:PHP-8.4from
iliaal:fix/gh-17144-fetch-dim-w-narrowing
Open

iliaal wants to merge 1 commit into
php:PHP-8.4from
iliaal:fix/gh-17144-fetch-dim-w-narrowing

Conversation

@iliaal

@iliaal iliaal commented Apr 8, 2026

Copy link
Copy Markdown
Member

Summary

FETCH_DIM_W stripped MAY_BE_ARRAY_EMPTY only inside a block guarded by key_type & (MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_KEY_STRING). When the key operand's type widened across loop iterations (e.g. from MAY_BE_ARRAY to include scalar types), key_type went from 0 to non-zero, causing the flag to be stripped on the second pass but not the first. This violated monotonicity and triggered the narrowing assertion.

Strips MAY_BE_ARRAY_EMPTY for write opcodes (W, RW, LIST_W) regardless of key_type, since a dimension write makes the array non-empty.

Fixes #17144

@dstogov dstogov 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 solution looks completely wrong.
The following PHP code doesn't modify $a (it throws exception) and $a is still empty.

$a = [];
$a[$a] = 1;

@iliaal
iliaal force-pushed the fix/gh-17144-fetch-dim-w-narrowing branch from cb5269e to f6f5b5b Compare April 13, 2026 23:52
@iliaal

iliaal commented Apr 13, 2026

Copy link
Copy Markdown
Member Author

Confirmed: $a = []; $a[$a] = 1; throws before the write, so unconditional stripping was wrong. Pushed a corrected version that strips MAY_BE_ARRAY_EMPTY only when the key is guaranteed valid: either op2 is IS_UNUSED (append) or t2 has no MAY_BE_ARRAY|MAY_BE_OBJECT bits. Once t2 gains ARRAY or OBJECT, the strip stays inactive, so the result type can only gain MAY_BE_ARRAY_EMPTY across iterations, never lose it. Test now covers both the original narrowing reproducer and the invalid-key throw path.

@iliaal
iliaal requested a review from dstogov April 14, 2026 01:16
@iliaal iliaal linked an issue Jun 28, 2026 that may be closed by this pull request
Comment thread Zend/tests/gh17144.phpt Outdated
function arrayKeyThrows() {
$a = [];
try {
$a[$a] = 1;

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 don't think this tests what you think it does. This compiles to ASSIGN_DIM

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.

Ah, indeed, that case never reached FETCH_DIM_W. Dropped it.

Comment thread Zend/tests/gh17144.phpt Outdated
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.jit=1254

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.

Suggested change
opcache.jit=1254
opcache.optimization_level=-1

I think it shoud have been this.

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.

Done.

@kamil-tekiela

Copy link
Copy Markdown
Member
 						}
 					}
-					if (opline->opcode != ZEND_FETCH_DIM_FUNC_ARG
-					 && (opline->op2_type == IS_UNUSED
-					  || !(t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT)))) {
-						tmp &= ~MAY_BE_ARRAY_EMPTY;
-					}
 				}
+				if (opline->opcode == ZEND_FETCH_DIM_W
+				 || opline->opcode == ZEND_FETCH_DIM_RW
+				 || opline->opcode == ZEND_FETCH_LIST_W) {
+					tmp &= ~MAY_BE_ARRAY_EMPTY;
+				}
 				if (!(tmp & MAY_BE_ARRAY)
 				 || (tmp & MAY_BE_ARRAY_KEY_ANY)

I think this might solve the problem.

Also, the test should be in ext/opcache/tests/opt/

FETCH_DIM_W stripped MAY_BE_ARRAY_EMPTY only when key_type had a valid
key bit. When the key operand widened across loop iterations from
MAY_BE_ARRAY to MAY_BE_ARRAY|MAY_BE_LONG, the strip switched on in the
second pass and the result type lost MAY_BE_ARRAY_EMPTY. Strip only when
the key cannot throw (append, or no MAY_BE_ARRAY|MAY_BE_OBJECT in the key
type), since such a key throws before the write and leaves the array
empty.

Fixes phpGH-17144
@iliaal
iliaal force-pushed the fix/gh-17144-fetch-dim-w-narrowing branch from f6f5b5b to a965cab Compare November 10, 2026 22:44
@iliaal

iliaal commented Nov 10, 2026

Copy link
Copy Markdown
Member Author

That is the first version of this PR, which Dmitry rejected above, so I kept the guarded strip. Moved the test to ext/opcache/tests/opt/.

@kamil-tekiela

Copy link
Copy Markdown
Member

That is the first version of this PR, which Dmitry rejected above, so I kept the guarded strip. Moved the test to ext/opcache/tests/opt/.

Yes, but I think this is the correct solution. Dmitry's remark was a bit vague. Disregarding the difference between FETCH_DIM_W and FETCH_DIM_W, there seems to be no way that the inference can proceed past the throw. If there is a try-catch then inference won't work anyway. Other code already seems to work based on these assumptions. But I leave it to Dmitry to decide as he is the expert here.

@iliaal

iliaal commented Nov 10, 2026

Copy link
Copy Markdown
Member Author

Agreed, DFA bails on any function with try/catch, so nothing observes the def past the throw.

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.

Assertion failure Zend/Optimizer/zend_inference.c:2086

3 participants