除了最常见的if条件语句之外,条件句还可以用运算符来表示。

三元运算符 ?:

?:是唯一的三元运算符。

1
2
<?php
$b = isset($a) ? $a : 1;

对应的CPG为:

1

我更喜欢转成规整的树状图查看:

ifTernary-PHPJoern

上面的代码相当于:

1
2
3
4
5
<?php
if (isset($a))
$b = $a;
else
$b = 1;

对应的CFG为:

2

if

我们可以看到,id=10的节点是statement $b=$a的根节点,id=17是statement $b=1的根节点。

php7新特性——null合并运算符??

而在php7中,也有一些新的特性引入,具体可以参看下面这2篇文章:

5 New Features in PHP 7

https://www.php.net/manual/en/migration70.new-features.php

php7中引入了一些新特性,比如:

Scalar type declarations:标量类型声明

Return type declarations:返回值类型声明

Null coalescing operator:null合并运算符,??

Spaceship operator:太空船操作符(组合比较符),<=>

Constant arrays using define():(通过define()定义常量数组)

Anonymous classes:匿名类

Unicode codepoint escape syntax:Unicode codepoint转译语法

and so on …

其中,和条件语句相关的新特性是null合并运算符,通常用??来表示,比如源码:

1
2
3
4
5
6
<?php
if (!empty($firstName))
$name = $firstName;
else
$name = "Guest";
?>

用null合并运算表示符来表示就是:

1
2
3
<?php
$name = $firstName ?? "Guest";
?>

我用PHP-Parser解析后得到的AST树是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
array(
0: Stmt_Expression(
expr: Expr_Assign(
var: Expr_Variable(
name: name
)
expr: Expr_BinaryOp_Coalesce(
left: Expr_Variable(
name: firstName
)
right: Scalar_String(
value: Guest
)
)
)
)
)

更具象点,用php-ast-visualizer可视化后的AST树为:

AST-if

这个ast树其实和下面的源码对应的ast是一模一样的:

1
2
3
4
5
6
<?php
if ($firstName)
$name = $firstName;
else
$name = 'Guest';
?>

但是这并不是合法的php语法,因为$firstName是未定义的,编译执行的时候会报错:

1
2
$ php test.php 
PHP Notice: Undefined variable: firstName in /home/sec/Documents/test.php on line 2

报错原因为:https://blog.csdn.net/mnmnwq/article/details/82465985

nodes.csv

5

rels.csv

6

生成的代码属性图为:

4

用更清晰的图来表示为:

if_coalescing

其中结点id=8, type=AST_COALESCE的结点就是表示??运算符,但是该结点被当成了顺序语句。在上图中并没有体现出分支的情况。

问题小结

上面的两个cases中,问题在于,PHPJoern将AST_CONDITIONAST_COALESCE节点当成了普通的赋值节点。如果我们真的要改进也很困难,像上面提到的正常的分支语句,我们能找到分支所需要的根节点,但是对于这种情况,我们找不到根节点,因为php2ast在将php文件解析为抽象语法树的时候,压根就没将其当成分支语句,所以没有解析出可用的分支节点,那Joern自然也无法根据php2ast解析的结果来处理这样特殊的条件语句。

如果说Joern无法正确处理三元运算条件语句和null合并运算符是因为没有分支需要的根节点,那么我们可以尝试将源码改成:

1
2
<?php
$b = isset($a) ? $b=$a : $b=1;

得到的代码属性图为:

3

转化为我自己更喜欢的图就是:

ifTernary2-PHPJoern

可以看到,在这种情况下,还是按照顺序语句处理。但是对于这种情况,是可以优化Joern的,我们自己也可以画出正确的控制流(我认为的正确的画法):

ifTernary2-right.svg