0x01 异常处理
在做代码分析的时候发现了一个有意思的点,样例代码如下:
<?php
1.function test() {
2. try {
3. throw new Exception('foo');
4. } catch (Exception $e) {
5. $a = 'hello';
6. return $a;
7. } finally {
8. $a = $a." world\n";
9. echo $a;
}
}
echo test();
?>
我们知道finally
会在return
之前执行,那么上图的执行顺序应该是
L5->L8->L9->L6
也就是
$a = 'hello'
$a = $a."world\n";
echo $a;
echo (return $a) //这里把外面的echo放进来的
那么我们的预期输出应该是
hello world
hello world
然而实际的输出却是:
hello world
hello
为什么return $a
中的$a
的值没变呢?
0x02 原因分析
直接上opcode
L0: V3 = NEW 1 string("Exception")
L1: SEND_VAL_EX string("foo") 1
L2: DO_FCALL
L3: THROW V3
L4: CATCH string("Exception") CV0($e)
L5: ASSIGN CV1($a) string("hello")
L6: T3 = QM_ASSIGN CV1($a)
L7: T2 = FAST_CALL L9 T3
L8: RETURN T3
L9: ASSIGN_CONCAT CV1($a) string(" world\n")
L10: ECHO CV1($a)
L11: FAST_RET T2
可以看到L8中的RETURN接收的是变量T3的值,也就是$a='hello'
的值。而在return之前执行的finally代码段的内容是通过FAST_CALL
进行调用的,即先执行finally代码段可以理解为进行了一个函数调用,在执行完L7跳转到L9进行执行,最后到L11遇到FAST_RET
回到T2(即L7)继续顺序执行。
因此传入finally代码段的$a是值传递,而不是引用传递,自然也不会影响RETURN的值
0x03 另一个样例
<?php
1.function test() {
2. try {
3. throw new Exception('foo');
4. } catch (Exception $e) {
5. $a = 'hello';
6. return $a;
7. } finally {
8. $a = $a." world\n";
9. echo $a;
10. return $a;
}
}
echo test();
?>
这个样例代码输出的就是:
hello world
hello world
查看opcode如下:
L0: V3 = NEW 1 string("Exception")
L1: SEND_VAL_EX string("foo") 1
L2: DO_FCALL
L3: THROW V3
L4: CATCH string("Exception") CV0($e)
L5: ASSIGN CV1($a) string("hello")
L6: T3 = QM_ASSIGN CV1($a)
L7: T2 = FAST_CALL L9 T3
L8: RETURN T3
L9: ASSIGN_CONCAT CV1($a) string(" world\n")
L10: ECHO CV1($a)
L11: DISCARD_EXCEPTION T2
L12: RETURN CV1($a)
L13: FAST_RET T2
根据上面的分析,这里也很简单,不过是在FAST_CALL
过程中遇到了RETURN
,因此程序执行终止。(所以说FAST_CALL只是一个看起来类似于函数调用的指令,实际上还在当前空间内执行)