当前位置: 首页 > 后端技术 > PHP

用Phan为你的PHP项目保驾护航——代码静态扫描

时间:2023-03-29 22:20:26 PHP

原文:我的个人博客https://mengkang.net/1356.html工作了两三年,技术停滞,迷茫,没有方向,不如看看下载我的livePHP进阶之路(金三银四跳槽必考,不告诉一般人)很多时候,在某些情况下,最大的优势会变成最大的劣势。PHP语法非常灵活,不需要编译。但是当项目比较复杂的时候,可能会导致一些意想不到的bug。背景分析不知你的项目是否遇到过类似的线上故障?例如继承类语法错误导致的故障File1classAnimal{public$hasLeg=false;}File2include"Animal.php";classDogextendsAnimal{protected$hasLeg=false;}$dog=newDog();php狗。phpFatalerror:AccessleveltoDog::$hasLegmustbepublic(asinclassAnimal)in/Users/mengkang/vagrant-develop/project/untitled1/Dog.phponline5(请注意,IDE并未指示存在是发布前的错误是的,我特地截图了)今天看代码的时候,看到一个变量被反复查询,就是用户是否是管理员。我想如果是这样的话,要不就放在第一次使用的成员变量中,以免后面重复查询。原来我在父类中定义的变量名$isAdmin,之前的代码已经在某个子类中单独定义了。父类中的公有属性和子类中的私有属性导致了这个失败。如果是java错误,则无法编译。但是php不需要编译,只要测试不覆盖刚才修改的文件,就不会发现这个问题,既是优点也是缺点。参数不符合预期。有时a.php、b.php、c.php这三个文件都引用了d.php的一个函数,只是修改了d.php中某个函数的参数个数。如果之前使用的3第一个文件没有变化,只是改了a.php,但是测试的时候没有覆盖b.php和c.php,所以上线的时候会触发bug和错误。你可能会认为这种错误太低级了,不会因为错误地将数组当成对象而发生在你身上,但根据我的经验,它确实会发生。高强度要求下,很容易复制粘贴一些东西,而且只复制了一半。而且恰好因为一些逻辑判断,在日常环境开发的时候,出现问题的地方没有执行。例如下面的代码:$article=$this->getParam('article');//假设复制下面的代码$isPowerEditer="xxxxxdemocode";if(!$isPowerEditer){if($article->getUserId()!=$uid){...}}因为复制源,$article是一个对象,所以调用了getUserId方法。但是上面的$article是从客户端获取的参数,不是对象。在非对象上调用成员函数getUserId(),自己测试时,因为if(!$isPowerEditer)的判断,没有执行。直到上线后才发现问题。把一个对象误当成一个数组不能把DataObject\Article类型的对象当作数组不禁反映,如果这个项目是java的话,肯定不会出现上面两个问题,因为在项目建的时候就已经失败了。不存在的数组不流行?多写了一个s,可能是因为外面有一个空包,所以IDE没有标记为错误。所以我们不能太相信IDE。自制轮子实验的思考与改进进一步思考,是否可以自己制作一个模拟编译的工具呢?我写了一个小demo,依赖nikic/php-parserhttps://github.com/nikic/PHP-...PHP-Parser可以将PHP代码解析成AST,方便我们做语法分析。例如,上面的示例文件1classAnimal{public$hasLeg=false;}文件2(Dog.php)include"Animal.php";classDogextendsAnimal{protected$hasLeg=false;}$dog=newDog();我们使用PHP-Parser做语法分析和检测,代码如下:includedirname(__DIR__)."/vendor/autoload.php";usePhpParser\Error;usePhpParser\Node\Stmt\Property;usePhpParser\ParserFactory;使用PhpParser\Node\Stmt\Class_;$code=file_get_contents("Dog.php");$parser=(newParserFactory)->create(ParserFactory::PREFER_PHP5);try{$ast=$parser->parse($代码);}catch(Error$error){echo"解析错误:{$error->getMessage()}\n";return;}$classCheck=newClassCheck($ast);$classCheck->extendsCheck();classClassCheck{/***@varClass_[]|null*/private$classTable;publicfunction__construct($nodes){foreach($nodesas$node){if($nodeinstanceofClass_){$name=$node->name;如果(!isset($this->classTable[$name])){$this->classTable[$name]=$node;}else{//哪里报错了?echo$node->getLine();}}}}publicfunctionextendsCheck(){foreach($this->classTableas$node){if(!$node->extends){继续;}$parentClassName=$node->extends->getFirst();if(!isset($this->classTable[$parentClassName])){exit($parentClassName."不存在");}$parentNode=$this->classTable[$parentClassName];foreach($node->stmtsas$stmt){if($stmtinstanceofProperty){//检查属性是否存在于父类中$this->propertyCheck($stmt,$parentNode);}}}}/***@paramProperty$property*@paramClass_$parentNode*/privatefunctionpropertyCheck($property,$parentNodede){foreach($parentNode->stmtsas$stmt){if($stmtinstanceofProperty){if($stmt->props[0]->name!=$property->props[0]->name){继续;}if($stmt->isProtected()&&$property->isPrivate()){echo$stmt->getLine()."\n";echo$property->getLine()."\n";}}}}}原理是继续分析解析出的AST,只是前人栽树,后人乘凉。这么完善的工具,大神们早已经为我们准备好了。使用已有的工具https://github.com/phan/phan,可以说是和上面介绍的nikic/php-parser同门,依赖nikic/php-astPHP扩展。先安装php-ast扩展,大致描述安装步骤gitclonehttps://github.com/nikic/php-astcdphp-ast/phpizesudo./configure--enable-astsudomakesudomakeinstallcd/etc/php.d#引入扩展sudovimast.ini#可以看到扩展php-m|grepastinstallcomposer大致描述安装步骤curl-sShttps://getcomposer.org/installer|phpinstallplanmkdirtestcdtest~/composer.pharrequire--dev"phan/phan:1.x"experiment实验1新建一个项目,随便写一段有问题的代码路径是src/a.phpa2(1);}/***@paramarray$b**@returnint*/privatefunctiona2($b){return$b+1;}}编写shell脚本#!/bin/bashfunctionlog(){echo-e-n"\033[01;35m[YUNQI]\033[01;31m"echo$@echo-e-n"\033[00m"}Color_Text(){echo-e"\e[0;$2m$1\e[0m"}Echo_Red(){echo$(Color_Text"$1""31")}Echo_Green(){echo$(Color_Text"$1""32")}Echo_Yellow(){echo$(Color_Text"$1""33")}:>file.listforfilein$(lssrc/*)doecho$file>>file.listdoneEcho_Green"文件列表:\n"Echo_Green"=========================\n"catfile.listEcho_Green"============================\n"Echo_Yellow"Phanrun\n"Echo_Yellow"============================\n"./vendor/bin/phan-ffile.list-ores.outEcho_Yellow"=========================\n“Echo_Red”错误日志\n“Echo_Red”==========================\n"catres.outEcho_Red"===========================\n"Executiveresulterrorclassincasedoesnotexistparametertypeerror语法运算类型推断实验二新增src/b.php