这篇文章给大家分享的是有关Phan代码静态扫描的案例分析的内容。小编觉得挺实用的,因此分享给大家做个参考。一起跟随小编过来看看吧。
很多时候,最大的优势在某些情况下就会变成最大的劣势。php语法非常灵活,也不用编译。但是在项目比较复杂的时候,可能会导致一些意想不到的错误。
<强>背景分析强>
不知道你的项目是否有遇到过类似的线上故障呢?比如
继承类语法错误导致的故障
<强>文件1 强>
类动物 { 公共hasLeg美元=false; }
<>强文件2 强>
包括“Animal.php"; 类狗延伸到动物 { 保护美元hasLeg=false; } 狗狗=new()美元;
php Dog.php 致命错误:访问级别狗::$ hasLeg必须公开(如类动物)/用户/mengkang/vagrant-develop/项目/untitled1/狗。php alt=" Phan代码静态扫描的案例分析">(注意IDE并没有提示有预发错误的哟,我专门截图)
今天在看代码的时候看到一个变量一直重复查询,就是用户是否是管理员的身份。我想既然这样,不然在第一次用的地方就放入到成员变量里,免得后面都重复查询。
结果发现我在父类定义的变量名isAdmin美元之前的代码已经在某一个子类里面单独定义过了。父类里是公共属性,而子类里是私人导致了这个故障。
如果是java这种错误,无法编译通过。但是php不需要编译,只要测试没有覆盖到刚刚修改的文件就不会发现这个问题,既是优势也是弱势。
<强>参数不符合预期强>
<强> 强>
有时候a.php, b.php, c.php三个文件都引用d.php的的一个函数,但是修改了d.php里面的一个函数的参数个数,如果前面使用的3个文件里面的没有改全,只改了a.php,而测试的时候又没有覆盖到b.php和c。php,那么上线了,就会触发错误和错误了。
<强>错把数组当对象强>
你可能认为这种错误太低级了,不可能发生在自己身上,但是根据我的经验的确会发生,高强度的需求之下,很容易复制粘贴一些东西,只复制一半,而且恰巧因为某些逻辑判断,自己在日常环境开发的时候,出现问题的地方没有被执行到。
比如下面这段代码:
=条这→美元getParam(& # 39;文章# 39;);//假设下面这段代码是复制的 $ isPowerEditer=皒xxxx演示代码“; 如果(! $ isPowerEditer) { 如果(文章→美元getUserId () !=$ uid) { … } }因为复制的来源处,美元的文章是一个对象,所以调用了getUserId的方法。但是上面美元的文章是一个从客户端获取的参数,不是对象。
调用成员函数getUserId () alt=" Phan代码静态扫描的案例分析">不能使用DataObject \文章作为数组类型的对象以前>不禁反思,如果这个项目是java的,肯定不会出现上面两个问题了,因为在项目构建的时候就已经没法通过了。
<强>不存在的数组强>
这也不飘红?多写了个年代呢,可能因为外面包了一个空所以IDE没有标记为错误吧,所以我们不能太相信IDE。
<强>思考与改进强>
<>强自造轮子实验强>
进一步思考,我们是否能够做一个工具来自己模拟编译呢?写了一个小演示,依赖nikic/php-parser
https://github.com/nikic/PHP-Parserphp-parser可以把php代码解析为AST,方便我们做语法分析。比如上面的例子
<强>文件1 强>
类动物 { 公共hasLeg美元=false; }<>强文件2 (Dog.php) 强>
包括“Animal.php"; 类狗延伸到动物 { 保护美元hasLeg=false; } 狗狗=new()美元;我们利用PHP-Parser做了语法解析检测,代码如下:
包括目录名(__DIR__)干净/供应商/autoload.php"; 使用PhpParser \错误; 使用PhpParser \ \ \支撑节点属性; 使用PhpParser \ ParserFactory; 使用PhpParser \ \ Class_节点\支撑; 代码=美元file_get_contents (“Dog.php"); 解析器=美元(新ParserFactory)→创建(ParserFactory:: PREFER_PHP5); 尝试{ ast=美元解析器→解析($代码); }捕捉(错误错误美元){ 回声“解析错误:{$错误→getMessage ()} \ n"; 返回; } (classCheck=new classCheck美元ast); 美元classCheck→extendsCheck (); 类ClassCheck {/* * * @var Class_ [] | null */私人classTable美元; 公共函数__construct(节点) { foreach(节点节点美元){ 如果(节点instanceof Class_美元){ name=美元节点→名称; 如果(!收取($ this→classTable[名字]美元)){ 美元$ this→classTable[名字]=$节点; 其他}{//报错哪里类重复了 echo $ node→getLine (); } } } } 公共函数extendsCheck () { foreach ($ this→classTable美元节点){ 如果(! $节点→扩展){ 继续; } parentClassName=美元节点→→扩展列表(); 如果(!收取($ this→classTable [$ parentClassName])) { 退出(parentClassName美元霸主地位;不存在“); } parentNode=这→美元classTable [$ parentClassName]; foreach(节点→美元支撑支撑美元){ 如果实例属性(支撑){//查看该属性是否存在于父类中 $ this→propertyCheck(支撑,parentNode美元); } } } }/* * * @param美元财产属性 * @param Class_ parentNode美元 */私有函数propertyCheck(财产,parentNode美元){ foreach (parentNode→美元支撑支撑美元){ 如果实例属性(支撑){ 如果美元支撑→道具[0]→名字!=$属性→道具[0]→名称){ 继续; } 如果美元支撑→isProtected (),,美元财产→isPrivate ()) { echo $支撑→getLine()的管理者;\ n"; echo $属性→getLine()的管理者;\ n"; } } } } }Phan代码静态扫描的案例分析