Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix GH-18107: Opcache CFG jmp optimization with try-finally breaks the exception table #18110

Closed
wants to merge 2 commits into from

Conversation

nielsdos
Copy link
Member

@nielsdos nielsdos commented Mar 18, 2025

If there's a try-finally where the try_op starts on a basic block with a single JMP, and the JMP optimization causes that basic block to become unreachable, then we update try_op.
In this case, there is no catch_op, so try_op is erroneously set to 0, we should instead set it to b->start.

This is a significantly reduced test case from the original code, so the code looks a bit unrealistic but is simple.

… the exception table

If there's a try-finally where the try_op starts on a basic block with a
single JMP, and the JMP optimization causes that basic block to become
unreachable, then we update try_op.
In this case, there is no catch_op, so try_op is erroneously set to 0,
we should instead set it to `b->start`.
@nielsdos nielsdos marked this pull request as ready for review March 18, 2025 23:01
@nielsdos nielsdos requested a review from dstogov as a code owner March 18, 2025 23:01
Copy link
Member

@dstogov dstogov left a comment

Choose a reason for hiding this comment

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

I'm not 100% sure.

You fix problem for for case without catch blocks. This looks right.

Please also check the case when we have catch block, but all try blocks are unreachable. Previously, we set try_op to the first catch block, but with your fix we may probably set it not to the first catch block.

@nielsdos
Copy link
Member Author

If the try block is unreachable, we first use the catch blocks if one of them is reachable:

if (op_array->try_catch_array[j].catch_op) {
end = blocks + block_map[op_array->try_catch_array[j].catch_op];
while (b != end) {
if (b->flags & ZEND_BB_REACHABLE) {
op_array->try_catch_array[j].try_op = b->start;
break;
}
b++;
}
}

This executes before using the finally blocks, i.e. before the code I changed.


I also tried some testing with PHP code.

  1. If I add a catch to the phpt test then nothing changes because the catch block is dead as well.

  2. In this case:

<?php

if (!isset($badvar)) {
    throw new Exception("Should happen");
}
try {
    goto foo;
} catch (Throwable $e) {
    foo:;
} finally {
    throw new Exception("Should not happen");
}
0000 T2 = ISSET_ISEMPTY_CV (isset) CV0($badvar)
0001 JMPNZ T2 0007
0002 V4 = NEW 1 string("Exception")
0003 SEND_VAL_EX string("Should happen") 1
0004 DO_FCALL
0005 THROW V4
0006 CV1($e) = CATCH string("Throwable")
0007 T6 = FAST_CALL 0009
0008 JMP 0014
0009 V7 = NEW 1 string("Exception")
0010 SEND_VAL_EX string("Should not happen") 1
0011 DO_FCALL
0012 THROW V7
0013 FAST_RET T6
0014 RETURN int(1)
EXCEPTION TABLE:
     0007, 0006, 0009, 0013

Prior to this PR, the try_op and catch_op pointed to the 0006 CV1($e) = CATCH string("Throwable") opline, whereas now we have the try_op pointing to the 0007 T6 = FAST_CALL 0009. However, I think this should be fine given that the CATCH op can't actually execute.

@dstogov
Copy link
Member

dstogov commented Mar 21, 2025

EXCEPTION TABLE:
     0007, 0006, 0009, 0013

Prior to this PR, the try_op and catch_op pointed to the 0006 CV1($e) = CATCH string("Throwable") opline, whereas now we have the try_op pointing to the 0007 T6 = FAST_CALL 0009. However, I think this should be fine given that the CATCH op can't actually execute.

Thanks for analyses.
This is what I was afraid of.
See the "exception table try_op - 0007 is above the catch_op - 0006.
This may confuse C code that iterated from try_op to catch_op. Actually the C loop posted above is affected.

The fix should be simple.

@nielsdos nielsdos closed this in 2ec8d37 Mar 21, 2025
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.

Opcache CFG jmp optimization with try-finally breaks the exception table
2 participants