中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Phan代碼靜態掃描的案例分析

發布時間:2020-11-12 11:00:24 來源:億速云 閱讀:181 作者:小新 欄目:編程語言

這篇文章給大家分享的是有關Phan代碼靜態掃描的案例分析的內容。小編覺得挺實用的,因此分享給大家做個參考。一起跟隨小編過來看看吧。

很多時候,最大的優勢在某些情況下就會變成最大的劣勢。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

Phan代碼靜態掃描的案例分析

(注意 IDE 并沒有提示有預發錯誤的喲,我專門截圖)

今天在看代碼的時候看到一個變量一直重復查詢,就是用戶是否是管理員的身份。我想既然這樣,不然在第一次用的地方就放入到成員變量里,免得后面都重復查詢。

結果發現我在父類定義的變量名$isAdmin,之前的代碼已經在某一個子類里面單獨定義過了。父類里是public屬性,而子類里是private導致了這個故障。

如果是 java 這種錯誤,無法編譯通過。但是 php 不需要編譯,只要測試沒有覆蓋到剛剛修改的文件就不會發現這個問題,既是優勢也是弱勢。

參數不符合預期

Phan代碼靜態掃描的案例分析

有時候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)的判斷導致沒有執行到里面去。直到上線之后才發現問題。

錯把對象當數組

Phan代碼靜態掃描的案例分析

Cannot use object of type DataObject\Article as array

不禁反思,如果這個項目是 java 的,肯定不會出現上面兩個問題了,因為在項目構建的時候就已經沒法通過了。

不存在的數組

Phan代碼靜態掃描的案例分析

這也不飄紅?多寫了個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.語法運算類型推斷

Phan代碼靜態掃描的案例分析

實驗2

新增一個src/b.php

<?php
class B{
}

執行結果

能過自動查找到class B了,不用我們做自動加載規則的指定

Phan代碼靜態掃描的案例分析

實驗3

剛剛兩個都是測試的單獨的腳本,沒有測試項目,其實Plan已經支持了。假如我有一個項目如下

Phan代碼靜態掃描的案例分析

我在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

Phan代碼靜態掃描的案例分析

如圖所示呢,說明根據項目的自動加載規則A,B,C三個類呢都被掃描到了。

感謝各位的閱讀!關于Phan代碼靜態掃描的案例分析就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

太和县| 义乌市| 册亨县| 玛纳斯县| 潞西市| 祥云县| 铁力市| 北辰区| 左贡县| 包头市| 交口县| 醴陵市| 六盘水市| 泰顺县| 桂阳县| 牡丹江市| 西华县| 新乐市| 开原市| 阿尔山市| 罗平县| 治多县| 红桥区| 连南| 社会| 玛曲县| 滕州市| 全州县| 财经| 中方县| 和平区| 呼和浩特市| 铁岭市| 丁青县| 双峰县| 饶阳县| 望谟县| 聂荣县| 阳西县| 肥乡县| 泸州市|