百分百源码网-让建站变得如此简单! 登录 注册 签到领金币!

主页 | 如何升级VIP | TAG标签

当前位置: 主页>网站教程>网页制作> 【Phan】代码静态扫描
分享文章到:

【Phan】代码静态扫描

发布时间:09/01 来源:未知 浏览: 关键词:
许多时候,最大的优势在某些状况下就会变成最大的劣势。PHP 语法非常灵敏,也不消编译。但是在项目比力复杂的时候,大概会致使一些意想不到的 bug。

背景剖析

不知道你的项目可否有碰到过相似的线上故障呢?比方

继承类语法错误致使的故障

文件1

class Animal
{
    public $hasLeg = false;
}

文件2

include "Animal.php";
class Dog extends Animal
{
    protected $hasLeg = false;
}
$dog = new Dog();
php Dog.php
Fatal error: Access level to Dog::$hasLeg must be public (as in class Animal) in /Users/mengkang/vagrant-develop/project/untitled1/Dog.php on line 5

09cbff756d0da9edde2a9a9e294f121.png

(留意 IDE 并没有提醒有预发错误的哟,我专门截图)

今天在看代码的时候看到一个变量不断反复查询,就是会员可否是治理员的身份。我想既然这样,不然在第一次用的地方就放入到成员变量里,免得后面都反复查询。

结果发明我在父类定义的变量名$isAdmin,此前的代码已经在某一个子类里面独自定义过了。父类里是public属性,而子类里是private致使了这个故障。

假如是 java 这种错误,没法编译通过。但是 php 不需要编译,只要测试没有覆盖到刚刚修改的文件就不会发明这个问题,既是优势也是弱势。

参数不相符预测

159fc171249eb29153d0b2426c2d91e.png

有时候a.php,b.php,c.php三个文件都援用d.php的的一个函数,但是修改了d.php里面的一个函数的参数个数,假如前面使用的3个文件里面的没有改全,只改了a.php,而测试的时候又没有覆盖到b.php和c.php,那么上线了,就会触发bug和错误了。

错把数组当对象

你大概认为这种错误太初级了,不成能发生在本人身上,但是按照我的经历确实会发生,高强度的需求之下,很容易复制粘贴一些东西,只复制一半。并且刚巧由于某些逻辑推断,本人在日常环境开发的时候,显现问题的地方没有被施行到。

比方下面这段代码:

$article = $this->getParam('article');
// 假设下面这段代码是复制的
$isPowerEditer = "xxxxx 演示代码";
if(!$isPowerEditer){
    if ($article->getUserId() != $uid)
    {
        ...
    }
}

由于复制的来源处,$article是一个对象,所以调取了getUserId的办法。但是上面的$article是一个从客户端猎取的参数,不是对象。

Call to a member function getUserId() on a non-object

而本人测试的时候,由于if(!$isPowerEditer)的推断致使没有施行到里面去。直到上线之后才发明问题。

错把对象当数组

ac3ac228a321cf3233e87e9bcaa3947.png

Cannot use object of type DataObject\Article as array

不禁深思,假如这个项目是 java 的,必定不会显现上面两个问题了,由于在项目构建的时候就已经没法通过了。

不存在的数组

4b26208a24c34bb8b7d445a1a664134.png

这也不飘红?多写了个s呢,大概由于外面包了一个empty所以IDE没有标志为错误吧。所以我们不克不及太信赖IDE。

思索与改善

自造轮子实验

进一步思索,我们可否能够做一个工具来本人模拟编译呢?写了一个小 demo ,依靠nikic/php-parser

https://github.com/nikic/PHP-Parser

PHP-Parser 可以把PHP代码解析为AST,利便我们做语法剖析。比方上面的例子

文件1

class Animal
{
    public $hasLeg = false;
}

文件2(Dog.php)

include "Animal.php";
class Dog extends Animal
{
    protected $hasLeg = false;
}
$dog = new Dog();

我们利用 PHP-Parser 做了语法解析检测,代码如下:

include dirname(__DIR__)."/vendor/autoload.php";
use PhpParser\Error;
use PhpParser\Node\Stmt\Property;
use PhpParser\ParserFactory;
use PhpParser\Node\Stmt\Class_;
$code = file_get_contents("Dog.php");
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP5);
try {
    $ast = $parser->parse($code);
} catch (Error $error) {
    echo "Parse error: {$error->getMessage()}\n";
    return;
}
$classCheck = new ClassCheck($ast);
$classCheck->extendsCheck();
class ClassCheck{
    /**
     * @var Class_[]|null
     */
    private $classTable;
    public function __construct($nodes)
    {
        foreach ($nodes as $node){
            if ($node instanceof Class_){
                $name = $node->name;
                if (!isset($this->classTable[$name])) {
                    $this->classTable[$name] = $node;
                }else{
                    // 报错哪里类反复了
                    echo $node->getLine();
                }
            }
        }
    }
    public function extendsCheck(){
        foreach ($this->classTable as $node){
            if (!$node->extends){
                continue;
            }
            $parentClassName = $node->extends->getFirst();
            if (!isset($this->classTable[$parentClassName])) {
                exit($parentClassName."不存在");
            }
            $parentNode = $this->classTable[$parentClassName];
            foreach ($node->stmts as $stmt){
                if ($stmt instanceof Property){
                    // 查看该属性可否存在于父类中
                    $this->propertyCheck($stmt,$parentNode);
                }
            }
        }
    }
    /**
     * @param Property $property
     * @param Class_ $parentNode
     */
    private function propertyCheck($property,$parentNode){
        foreach ($parentNode->stmts as $stmt){
            if ($stmt instanceof Property){
                if ($stmt->props[0]->name != $property->props[0]->name){
                    continue;
                }
                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扩展

大约描写安置步骤

git clone https://github.com/nikic/php-ast
cd php-ast/
phpize
sudo ./configure --enable-ast
sudo make
sudo make install
cd /etc/php.d
# 引入扩展
sudo vim ast.ini
# 就能看到扩展啦
php -m | grep ast

安置 composer

大约描写安置步骤

curl -sS https://getcomposer.org/installer | php

安置plan

mkdir test
cd test
~/composer.phar require --dev "phan/phan:1.x"

实验

实验1

创建个项目,随意写个有问题的代码

途径是src/a.php

<?php
class A extends B
{
    public function a1()
    {
        return $this->a2(1);
    }
    /**
     * @param array $b
     *
     * @return int
     */
    private function a2($b)
    {
        return $b + 1;
    }
}

写个shell足本

#!/bin/bash
function log()
{
    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.list
for file in $(ls src/*)
do
  echo $file >> file.list
done
Echo_Green "file list:\n"
Echo_Green "========================\n"
cat file.list
Echo_Green "========================\n"
Echo_Yellow "Phan run\n"
Echo_Yellow "========================\n"
./vendor/bin/phan -f file.list -o res.out
Echo_Yellow "========================\n"
Echo_Red "error log\n"
Echo_Red "========================\n"
cat res.out
Echo_Red "========================\n"

施行结果

案例中的错误

1.类不存在

2.参数类型错误

3.语法运算类型推断

1794fb71fadacc2d91df8da0b124c56.png

实验2

新增一个src/b.php

<?php
class B{
}

施行结果

能过主动查寻到class B了,不消我们做主动加载规则的指定

d2f7a8f2dca8da87f24775cec9e45b2.png

实验3

刚刚两个都是测试的独自的足本,没有测试项目,其实Plan已经支撑了。假设我有一个项目如下

656645af70b2035eb8bf1707986baef.png

我在composer.json里面指定主动加载规则

{
  "require-dev": {
    "phan/phan": "1.x"
  },
  "autoload": {
    "psr-4": {
      "Mk\\": "src"
    }
  }
}

然后在项目根名目施行

./vendor/bin/phan --init --init-level=3

然后就会生成默许的配置文件在.phan名目里,最后就可以施行静态检测命令了

./vendor/bin/phan --progress-bar

d033fb376300e7d98bdfc33700a3382.png

如图所示呢,说明按照项目的主动加载规则A,B,C三个类呢都被扫描到了。

以上就是【Phan】代码静态扫描的具体内容,更多请关注百分百源码网其它相关文章!

打赏

打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

百分百源码网 建议打赏1~10元,土豪随意,感谢您的阅读!

共有154人阅读,期待你的评论!发表评论
昵称: 网址: 验证码: 点击我更换图片
最新评论

本文标签

广告赞助

能出一分力是一分吧!

订阅获得更多模板

本文标签

广告赞助

订阅获得更多模板