# Line numbers for "finally"

- Thread: https://externals.io/message/89926
- Original archive: https://news-web.php.net/php.internals/89926
- Started by Derick Rethans on 2015-12-30T19:55:26+00:00
- Messages: 6
- Reply by email to internals@lists.php.net
- More for agents: search with https://externals.io/search.md?q={query} · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

---

## #89926 — Derick Rethans — 2015-12-30T19:55:26+00:00

URL: https://externals.io/message/89926#89926

Hi!

While investigating an issue with Xdebug and fast_call/fast_ret, I 
noticed that the opcodes "associated" with the "finally" statement, are 
rolled up in the previous line.

The code:

  1 <?php
  2 function extractFile()
  3 {
  4     try {
  5         echo "try\n";
  6     } catch (Catch1 $e) {
  7         echo "catch1\n";
  8     } catch (Catch2 $e) {
  9         echo "catch2\n";
 10     } finally {
 11         echo "finally\n";
 12     }
 13     echo "end\n";
 14 }
 15 
 16 extractFile();
 17 ?>


Produces:

line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   5     0  E >   ECHO                                                     'try%0A'
         1      > JMP                                                      ->7
   6     2  E > > CATCH                                                    'Catch1', !0, ->5
   7     3    >   ECHO                                                     'catch1%0A'
         4      > JMP                                                      ->7
   8     5  E > > CATCH                                                    'Catch2', !0
   9     6    >   ECHO                                                     'catch2%0A'
         7    > > FAST_CALL                                                ->9
         8*       JMP                                                      ->11
  11     9    >   ECHO                                                     'finally%0A'
        10      > FAST_RET                                                 
  13    11    >   ECHO                                                     'end%0A'
  14    12      > RETURN                                                   null


The FAST_CALL/JMP instructions should really be linked to line 10. Not 
doing so produces confusing results while doing code coverage, where it now
shows that the "echo catch2\n" on line 9 is executed. This is of course not
the case, but the linking of FAST_CALL and JMP to line 9 makes it look like
this. Is it possible to change this so thta the FAST_CALL and JMP are linked
to line 10 instead?

And secondly, I am struggeling with where FAST_CALL and FAST_RET can jump to.
Right now, I have: 

https://github.com/derickr/vld/commit/9cf01bba0a1aeef6a261c6c85b238552215a9f0b#diff-286f7620179e1ee0a20e81523d91ff24R1036

+#if `PHP_VERSION_ID` >= 50500
+       } else if (opcode.opcode == ZEND_FAST_CALL) {
+#if `PHP_VERSION_ID` >= 70000
+               *jmp1 = VLD_ZNODE_JMP_LINE(opcode.op1, position, base_address);
+#else
+               *jmp1 = ((long) VLD_ZNODE_ELEM(opcode.op1, jmp_addr) - (long) base_address) / sizeof(zend_op);
+#endif
+               if (opcode.extended_value) {
+                       *jmp2 = VLD_ZNODE_ELEM(opcode.op2, opline_num);
+               }
+               return 1;
+       } else if (opcode.opcode == ZEND_FAST_RET) {
+               *jmp1 = position + 1;
+               if (opcode.extended_value) {
+                       *jmp2 = VLD_ZNODE_ELEM(opcode.op2, opline_num);
+               }
+               return 1;
+#endif

Which seems to work, although I am unsure about the "+ 1" for FAST_RET.

I also never see anything for opcode.extended_value, and hence the *jmp is
unset. This also returns dumping opcodes with this bit of code (code slightly
lifted from dbg):

https://github.com/derickr/vld/commit/9cf01bba0a1aeef6a261c6c85b238552215a9f0b#diff-286f7620179e1ee0a20e81523d91ff24R684

+#if `PHP_VERSION_ID` >= 50600
+       switch (op.opcode) {
+               case ZEND_FAST_RET:
+                       if (op.extended_value == ZEND_FAST_RET_TO_FINALLY) {
+                               fetch_type = "to_finally";
+                       } else if (op.extended_value == ZEND_FAST_RET_TO_CATCH) {
+                               fetch_type = "to_catch";
+                       }
+                       break;
+               case ZEND_FAST_CALL:
+                       if (op.extended_value == ZEND_FAST_CALL_FROM_FINALLY)
{
+                               fetch_type = "from_finally";
+                       }
+                       break;
+       }
+#endif
+

Any hints?

cheers,
Derick
-- 
http://derickrethans.nl | http://xdebug.org
Like Xdebug? Consider a donation: http://xdebug.org/donate.php
twitter: @derickr and @xdebug
Posted with an email client that doesn't mangle email: alpine

---

## #89930 — Nikita Popov — 2015-12-30T23:10:54+00:00

URL: https://externals.io/message/89926#89930 · In reply to: #89926

> Hi!
>
> While investigating an issue with Xdebug and fast_call/fast_ret, I
> noticed that the opcodes "associated" with the "finally" statement, are
> rolled up in the previous line.
>
> The code:
>
>   1 <?php
>   2 function extractFile()
>   3 {
>   4     try {
>   5         echo "try\n";
>   6     } catch (Catch1 $e) {
>   7         echo "catch1\n";
>   8     } catch (Catch2 $e) {
>   9         echo "catch2\n";
>  10     } finally {
>  11         echo "finally\n";
>  12     }
>  13     echo "end\n";
>  14 }
>  15
>  16 extractFile();
>  17 ?>
>
>
> Produces:
>
> line     #* E I O op                           fetch          ext  return
> operands
>
> -------------------------------------------------------------------------------------
>    5     0  E >   ECHO
>  'try%0A'
>          1      > JMP
> ->7
>    6     2  E > > CATCH
> 'Catch1', !0, ->5
>    7     3    >   ECHO
>  'catch1%0A'
>          4      > JMP
> ->7
>    8     5  E > > CATCH
> 'Catch2', !0
>    9     6    >   ECHO
>  'catch2%0A'
>          7    > > FAST_CALL
> ->9
>          8*       JMP
> ->11
>   11     9    >   ECHO
>  'finally%0A'
>         10      > FAST_RET
>   13    11    >   ECHO
>  'end%0A'
>   14    12      > RETURN
>  null
>
>
> The FAST_CALL/JMP instructions should really be linked to line 10. Not
> doing so produces confusing results while doing code coverage, where it now
> shows that the "echo catch2\n" on line 9 is executed. This is of course not
> the case, but the linking of FAST_CALL and JMP to line 9 makes it look like
> this. Is it possible to change this so thta the FAST_CALL and JMP are
> linked
> to line 10 instead?
>

Yeah, you're right. Should be fixed with
https://github.com/php/php-src/commit/b3afeeabefc4777ec4797a7e2c3688e9e20be4cc
.


> And secondly, I am struggeling with where FAST_CALL and FAST_RET can jump
> to.
> Right now, I have:
>
>
> https://github.com/derickr/vld/commit/9cf01bba0a1aeef6a261c6c85b238552215a9f0b#diff-286f7620179e1ee0a20e81523d91ff24R1036
>
> +#if `PHP_VERSION_ID` >= 50500
> +       } else if (opcode.opcode == ZEND_FAST_CALL) {
> +#if `PHP_VERSION_ID` >= 70000
> +               *jmp1 = VLD_ZNODE_JMP_LINE(opcode.op1, position,
> base_address);
> +#else
> +               *jmp1 = ((long) VLD_ZNODE_ELEM(opcode.op1, jmp_addr) -
> (long) base_address) / sizeof(zend_op);
> +#endif
> +               if (opcode.extended_value) {
> +                       *jmp2 = VLD_ZNODE_ELEM(opcode.op2, opline_num);
> +               }
> +               return 1;
> +       } else if (opcode.opcode == ZEND_FAST_RET) {
> +               *jmp1 = position + 1;
> +               if (opcode.extended_value) {
> +                       *jmp2 = VLD_ZNODE_ELEM(opcode.op2, opline_num);
> +               }
> +               return 1;
> +#endif
>
> Which seems to work, although I am unsure about the "+ 1" for FAST_RET.
>

For PHP 7 only: FAST_CALL always jumps to op1. op2 is not a jmp addr, it's
a try_catch_array offset. For FAST_RET there are no jump addresses encoded
in the opline. It will either jump back to one past the invoking FAST_CALL
(of which there may be multiple), or (if finally is executed due to an
uncaught exception) it will jump back to the next applicable catch or
finally or leave the function.

Nikita


> I also never see anything for opcode.extended_value, and hence the *jmp is
> unset. This also returns dumping opcodes with this bit of code (code
> slightly
> lifted from dbg):
>
>
> https://github.com/derickr/vld/commit/9cf01bba0a1aeef6a261c6c85b238552215a9f0b#diff-286f7620179e1ee0a20e81523d91ff24R684
>
> +#if `PHP_VERSION_ID` >= 50600
> +       switch (op.opcode) {
> +               case ZEND_FAST_RET:
> +                       if (op.extended_value == ZEND_FAST_RET_TO_FINALLY)
> {
> +                               fetch_type = "to_finally";
> +                       } else if (op.extended_value ==
> ZEND_FAST_RET_TO_CATCH) {
> +                               fetch_type = "to_catch";
> +                       }
> +                       break;
> +               case ZEND_FAST_CALL:
> +                       if (op.extended_value ==
> ZEND_FAST_CALL_FROM_FINALLY)
> {
> +                               fetch_type = "from_finally";
> +                       }
> +                       break;
> +       }
> +#endif
> +
>
> Any hints?
>
> cheers,
> Derick
> --
> http://derickrethans.nl | http://xdebug.org
> Like Xdebug? Consider a donation: http://xdebug.org/donate.php
> twitter: @derickr and @xdebug
> Posted with an email client that doesn't mangle email: alpine

---

## #89934 — Derick Rethans — 2015-12-31T00:09:21+00:00

URL: https://externals.io/message/89926#89934 · In reply to: #89930

Hi!



> 
> 
> > […]

<snip>

> > […]
> 
> Yeah, you're right. Should be fixed with 
> https://github.com/php/php-src/commit/b3afeeabefc4777ec4797a7e2c3688e9e20be4cc 
> .

Thanks — I'll give that a shot tomorrow. Did you merge that to 7.0 too?

> > […]
> 
> For PHP 7 only: FAST_CALL always jumps to op1. op2 is not a jmp addr, it's
> a try_catch_array offset. For FAST_RET there are no jump addresses encoded
> in the opline. It will either jump back to one past the invoking FAST_CALL
> (of which there may be multiple), or (if finally is executed due to an
> uncaught exception) it will jump back to the next applicable catch or
> finally or leave the function.

Hmm, that's more complicated than I thought. How would I fix that code? 
(PR welcome :D )

cheers,
Derick

---

## #89944 — Nikita Popov — 2015-12-31T16:48:51+00:00

URL: https://externals.io/message/89926#89944 · In reply to: #89934

> Hi!
>
> 
>
> > […]
>
> <snip>
>
> > […]
> https://github.com/php/php-src/commit/b3afeeabefc4777ec4797a7e2c3688e9e20be4cc
> > […]
>
> Thanks — I'll give that a shot tomorrow. Did you merge that to 7.0 too?
>
> > […]
> https://github.com/derickr/vld/commit/9cf01bba0a1aeef6a261c6c85b238552215a9f0b#diff-286f7620179e1ee0a20e81523d91ff24R1036
> > […]
> base_address);
> > […]
> (long) base_address) / sizeof(zend_op);
> > […]
> it's
> > […]
> encoded
> > […]
> FAST_CALL
> > […]
>
> Hmm, that's more complicated than I thought. How would I fix that code?
> (PR welcome :D )
>

I don't know what you need this for, but maybe you can use the same "good
enough" approximation we use for our internal control flow analysis, namely
simply say that FAST_CALL either jumps to op1 or to the next instruction,
while FAST_RET is treated as a terminator instruction (like RETURN). This
does not accurately model control flow, but it may be sufficient for your
purposes.

Nikita

---

## #89964 — Derick Rethans — 2016-01-02T11:59:17+00:00

URL: https://externals.io/message/89926#89964 · In reply to: #89944

> 
> 
> > […]
> 
> I don't know what you need this for, but maybe you can use the same 
> "good enough" approximation we use for our internal control flow 
> analysis, namely simply say that FAST_CALL either jumps to op1 or to 
> the next instruction, while FAST_RET is treated as a terminator 
> instruction (like RETURN). This does not accurately model control 
> flow, but it may be sufficient for your purposes.

I need to analyse every possible branch, and path through the code in 
order to do dead code analysis (for which I think your simplified 
option works), but also for path coverage, for which I don't think your 
algorithm works. Xinchen alluded on IRC:

15:21 <@laruence> Derick, it used for calling "finally block"
15:22 <@laruence> it has the entry opline of finally block  as opline->op1
15:22 <@laruence> and in the end of finally block ,there always be a 
                  ZEND_FAST_RET opline, which will return to proper address 
                  after finally block codes is executed

To me that indicates that the FAST_RET can jump back to any location 
where FAST_CALL was *called* from (+1)?

In any case, I think my current code does what you describe, except for 
FAST_RET, as that does not act like a RETURN - is there any reason why 
my approach wouldn't work?

Some more observations and questions:

FAST_CALL:
*jmp1 = XDEBUG_ZNODE_JMP_LINE(opcode.op1, position, base_address);

- But what do do for *jmp2 - if extended_value is set?

- I can't come up with PHP code that sets extended_value though. Can you?

FAST_RET:
*jmp1 = position + 1;

- But you indicate that that really should be *jmp1 = XDEBUG_JMP_EXIT?

- Or, it should jump back to something else?
- But how do I know to which possible opline(s) it is going to jump back to?
- Is there a list of these somewhere?
- I can't come up with PHP code that sets extended_value here either. 
  Can you?

cheers,
Derick

---

## #89965 — Xinchen Hui — 2016-01-02T14:12:01+00:00

URL: https://externals.io/message/89926#89965 · In reply to: #89964

Hey:




> 
>
> > […]
>
> I need to analyse every possible branch, and path through the code in
> order to do dead code analysis (for which I think your simplified
> option works), but also for path coverage, for which I don't think your
> algorithm works. Xinchen alluded on IRC:
>
> 15:21 <@laruence> Derick, it used for calling "finally block"
> 15:22 <@laruence> it has the entry opline of finally block  as opline->op1
> 15:22 <@laruence> and in the end of finally block ,there always be a
>                   ZEND_FAST_RET opline, which will return to proper address
>                   after finally block codes is executed
>
> To me that indicates that the FAST_RET can jump back to any location
> where FAST_CALL was *called* from (+1)?
>
> In any case, I think my current code does what you describe, except for
> FAST_RET, as that does not act like a RETURN - is there any reason why
> my approach wouldn't work?
>
> Some more observations and questions:
>
> FAST_CALL:
> *jmp1 = XDEBUG_ZNODE_JMP_LINE(opcode.op1, position, base_address);
>
> - But what do do for *jmp2 - if extended_value is set?

if extended_value is set(FROM_FINALLY) that means this finally block is
inside another finally block

I am not sure what *jmp2 means here? ZEND_FAST_CALL always jmp to one
place. but  in case FROM_FINALLY it has outer FAST_CALL's opline in op2 .


> - I can't come up with PHP code that sets extended_value though. Can you?
>
try  {
} finally {
   try  {
   } finally /* this fast_call will set with FROM_FINALLY*/
   {}
}

>
> FAST_RET:
> *jmp1 = position + 1;
>
> - But you indicate that that really should be *jmp1 = XDEBUG_JMP_EXIT?
>
> - Or, it should jump back to something else?
> - But how do I know to which possible opline(s) it is going to jump back
> to?

- Is there a list of these somewhere?

I am afarid there is not, let me try to summary it(I will try to make it
clean, but mine poor english :<):

ZEND_FAST_CALL opcode is used for set a proper return address for
ZEND_FAST_RET.

there are two ways can entry a finally block, ZEND_FAST_CALL and
ZEND_HANDLE_EXCEPTION:

1) ZEND_FAST_CALL:  it set the return address to it's self (it will always
follow by a JMP, which will JMP to end of finall block).
2) ZEND_HANDLE_EXCEPTION, if there is catch block then jmp to it(in the end
of the catch block there will be a JMP to finally block), if no catch block
is matched and there is a finally block, then set fast_call_var->u2.lineno
== -1, and stash the EG(exception) into Z_OBJ_P(fast_call_var), then jmp to
the finally block.

for FAST_RET:
   in fast_ret, if fast_call_var->u2.lineno != -1, then it means a jmp
address, simply jmp to it.

   if  fast_call->u2.lineno == -1, then there must be a un-handle
exception(stored in Z_OBJ_P(fast_call_var)),  so:

   if RET_TO_FINALLY(see blow), means there is another finally block needs
to be run, jmp to the  ZEND_FAST_CALL opline of that finally block, in
which opline handle, if opline->extended_value
== ZEND_FAST_CALL_FROM_FINALLY(see below) and obviously current
Z_OBJ_P(fast_call_var) is set, then goes into the finally block behavior
like the way ZEND_HANDLE_EXCEPTION did, which is set the
fast_call->u2.lineno to -1, and jmp into the finally block.

  otherwise, restore the exception,  then goes like a new  exception is
throw(jmp to a CATCH block or leave current function which will result
ZEND_HANDLE_EXCEPTION again).



- I can't come up with PHP code that sets extended_value here either.
>
try  {
   try {
   } finally {

   } /* this fast_ret will set to RET_TO_CATCH */

}  catch () {
}

try  {
   try {
   } finally {

   } /* this fast_ret will set to RET_TO_FINALLY */

}  finally {
}

thanks

>   Can you?


thanks

>
> cheers,
> Derick
>



-- 
Xinchen Hui
@Laruence
http://www.laruence.com/
