擴展阿里p3c實現自定義代碼規範檢查

 前段時間fastjson報出了漏洞,只要打開setAutoType特性就會存在風險,本身測試環境的一個項目被揪出來了-_-!。雖然改動很小,但就是以爲憋屈。fastjson仍是挺好的,想着禁用的話太惋惜,用的話又要注意安全,就想着找款工具提示下在用fastjson的時候不要打開這個特性。恰好阿里開源了p3c(https://github.com/alibaba/p3c),一款代碼規範的檢查工具,有對應的ide插件,能在編碼過程當中對設置的規則進行提示,便打算對它進行拓展,增長對fastjson檢查是否打開setAutoType特性的檢查。java

 p3c主要包括3部分:node

  • PMD實現(p3c-pmd):使用PMDhttps://pmd.github.io/來實現代碼規範檢查
  • Intellij IDEA插件
  • Eclipse插件

 《阿里巴巴Java開發手冊》中的大部分規則都是在p3c-pmd模塊中實現的,該部分也是這節研究的主要部分。git

1. PMD

 p3c使用了PMD。PMD是一款靜態代碼掃描工具,該工具能夠作到檢查Java代碼中是否含有未使用的變量、是否含有空的抓取塊、是否含有沒必要要的對象等。PMD使用JavaCC生成解析器來解析源代碼並生成AST(抽象語法樹),經過對AST的檢查能夠直接從源代碼文本層面來對代碼進行檢查,在PMD內部稱爲規則。便是否符合規則指的是,窮舉源碼各類可能的寫法,而後在AST上檢查是否出現。而規則的實現,重點便在對AST的處理上。github

1.1. AST

 關於AST的介紹網上有不少,能夠直接搜索,這裏重要提兩點:express

  • AST是源代碼的抽象語法結構的樹狀表示
  • 抽象語法樹並不依賴於原語言的語法,也就是說同語法分析階段所採用的上下文無關

 PMD使用JavaCC來生成AST。關於JavaCC也能夠在網上查看相關資料,這裏很少介紹,只要知道JavaCC是一個詞法分析生成器和語法分析生成器便行。json

1.2. 自定義規則

 PMD官方文檔介紹了自定義規則的實現步驟,過程比較清晰,這裏不贅述,只介紹下本文須要設計的步驟。segmentfault

1.2.1. 定義規則集

 PMD的規則須要配置在XML文件中安全

  • 新建以下的空文件表示規則集
<?xml version="1.0"?>
        <ruleset name="Custom Rules"
        xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
    
        <description>
            My custom rules
        </description>
    
    
        <!-- Your rules will come here -->

    </ruleset>
  • 在上面的 ruleset 標籤中引用自定義規則微信

    <rule ref="pathToYourOwnClass" />
  • 配置規則集
1.2.2. 配置規則

 能夠在 rule 標籤中對某一規則進行配置app

  • 配置提示消息和告警級別

    <rule ref="pathToYourOwnClass"
      message="message to show" >
      <priority>5</priority>
    </rule>

    告警優先級分爲1-5個level,1最高,5最低

  • 配置運行參數

    能夠經過 properties propertie 對類屬性賦值

1.2.3. 編寫規則

 規則的編寫比較簡單,PMD已經給咱們作好了配套的開發框架和工具,只要肯定後規則出現的狀況,按照固定的模式去編寫便可。

  • 肯定實現方式

    可使用純Java方式實現,也可使用XPath方式實現。

    對於純Java方式,PMD框架自己實現了對AST數的遍歷,用戶只要在遍歷各個節點的時候,對自定義規則的各類狀況進行分析判斷便可,過程相似與DOM文件的SAX解析,以流和事件的方式來解析AST內容。

    對於XPath方式,則是將AST做爲一個XML數,以XPath的方式來遍歷解析內容。

  • 根據測試代碼肯定可能出現的狀況

    PMD自帶了一個designer,能夠用來生成對應代碼的AST內容。對於本文須要達到的效果,涉及到以下代碼:

    import com.alibaba.fastjson.parser.ParserConfig;
    
    public class NegativeExample {
    
        public static void main(String[] args) {
            ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        }
    }

    在designer中能夠獲得以下的AST內容

    file

    具體內容這裏就不貼出來了,能夠下載desigin而後貼上上面的代碼就會有。

    對於本文的需求,須要肯定在有import ParserConfig 類的時候,調用了 setAutoTypeSupport 方法且參數爲 true 。固然這個條件是不夠嚴謹的,還須要判斷是否調用了 ParserConfig ,也要考慮有經過import導入直接寫全限定名的。但考慮到通常的寫法,以及ide的格式化處理,這樣處理已經能夠知足大部分場景了。

  • 編寫規則實現類

    肯定了規則的判斷條件,就能夠着手寫代碼了。

    對於Java方式,能夠直接繼承 net.sourceforge.pmd.lang.java.rule.AbstractJavaRule 類,而後重寫各類節點的visit方法便可。

    例如以下,用於判斷是否有import ParserConfig

    private final String PARSER_CONFIG_IMPORT_NAME = "com.alibaba.fastjson.parser.ParserConfig";
    
    private boolean hasImport = false;
    
    @Override
    public Object visit(ASTImportDeclaration node, Object data) {
        if (PARSER_CONFIG_IMPORT_NAME.equals(node.getImportedName())) {
            hasImport = true;
        }
        return super.visit(node, data);
    }

    對於XPath方式,能夠經過繼承 net.sourceforge.pmd.lang.rule.XPathRule 類,重寫 setXPath 方法設定對應的XPath便可。上面提到過,能夠經過 property 配置對類的屬性進行賦值,於是,對於XPat方式,能夠在xml配置中進行以下設置來啓用XPath。

    <rule name="My Rule"
          language="java"
          message="violation message"
          class="net.sourceforge.pmd.lang.rule.XPathRule">

Rule Description

</description>
     <priority>3</priority>
     <properties>
         <property name="xpath">
             <value>
             <![CDATA[

--- here comes your XPath expression
]]>

</value>
         </property>
     </properties>
```
1.2.4. 測試規則

 PMD推薦對於每一個規則,至少要有一個正向和逆向的測試用例,來驗證規則出現和不出現的狀況。對於規則的測試,PMD也提供了一套框架,只要按照約定好的方式添加xml測試文件便可。

 PMD約定了幾個規則,用來加載測試案例

  • 測試類要繼承 net.sourceforge.pmd.testframework.PmdRuleTst 類,該整合了Junt,能夠在裏面增長鬚要的測試方法。
  • 對於在 src/test/resource 和測試類對應的路徑下增長一個xml目錄,在增長同第一步同名的xml文件,該文件用於書寫測試集。

    例如官網給出的例子

    規則實現類路徑以下:

net.sourceforge.pmd.lang.java.rule.bestpractices.AbstractClassWithoutAbstractMethodTest

```

測試案例集以下:

```
src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AbstractClassWithoutAbstractMethod.xml

```

xml的規則則能夠參考官網介紹,這裏再也不贅述。

2. p3c-pmd

 代碼規範實現的主要模塊,使用pmd來實現。p3c-pmd模塊在代碼組織上很工整,能夠按照相同的模式增長自定義的規則/規則集。

 對於本文需求,打算在該模塊的基礎上增長一個extend模塊,用於實現自定義規則集。以下,爲對應的源碼路徑好規則集路徑

file

file

 本文采用純Java方式實現規則,按照1.2.3.節的描述,能夠獲得以下的實現類

public class AvoidFastJsonAutoTypeSupportRule extends AbstractAliRule {

    private final String PARSER_CONFIG_IMPORT_NAME = "com.alibaba.fastjson.parser.ParserConfig";

    private final String SET_AUTO_TYPE_SUPPORT_NAME = "setAutoTypeSupport";

    private boolean hasImport = false;


    @Override
    public Object visit(ASTImportDeclaration node, Object data) {
        if (PARSER_CONFIG_IMPORT_NAME.equals(node.getImportedName())) {
            hasImport = true;
        }
        return super.visit(node, data);
    }

    @Override
    public Object visit(ASTPrimaryExpression node, Object data) {
        if (hasImport) {
            int size = node.jjtGetNumChildren();
            for (int i = 0; i < size; i++) {
                Node child = node.jjtGetChild(i);
                String imageName = null;
                if (child instanceof ASTPrimaryPrefix) {
                    ASTPrimaryPrefix prefix = (ASTPrimaryPrefix) child;
                    imageName = prefix.jjtGetChild(0).getImage();
                }else if (child instanceof ASTPrimarySuffix){
                    ASTPrimarySuffix suffix = (ASTPrimarySuffix) child;
                    imageName = suffix.getImage();
                }

                if (imageName == null) {
                    continue;
                }else if (imageName.endsWith(SET_AUTO_TYPE_SUPPORT_NAME)){
                    ASTPrimarySuffix argumentSuffix = (ASTPrimarySuffix) node.jjtGetChild(i + 1);
                    try {
                        List<Node> booleanArgs = argumentSuffix.findChildNodesWithXPath("//PrimaryPrefix/Literal/BooleanLiteral");
                        if (booleanArgs.size() == 1) {
                            ASTBooleanLiteral booleanLiteral = (ASTBooleanLiteral) booleanArgs.get(0);
                            if (booleanLiteral.isTrue()) {
                                ViolationUtils.addViolationWithPrecisePosition(this, argumentSuffix, data,
                                    I18nResources.getMessage("java.extend.AvoidFastJsonAutoTypeSupportRule.rule.msg" ));
                            }
                        }
                    } catch (JaxenException e) {
                        e.printStackTrace();
                    } finally {
                        break;
                    }
                }
            }
        }
        return super.visit(node, data);
    }
}

 對應規則集中的配置爲

<rule name="AvoidFastJsonAutoTypeSupportRule" language="java"
    message="java.extend.AvoidFastJsonAutoTypeSupportRule.rule.msg"
    class="com.alibaba.p3c.pmd.lang.java.rule.extend.AvoidFastJsonAutoTypeSupportRule">
    <description>java.extend.AvoidFastJsonAutoTypeSupportRule.rule.desc</description>
    <priority>1</priority>
    <example>
      <![CDATA[
Negative example:
      import com.alibaba.fastjson.parser.ParserConfig;

      public class NegativeExample {

          public static void main(String[] args) {
              ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
          }
      }
]]>
    </example>
  </rule>

 這裏有幾點須要注意的,類 AvoidFastJsonAutoTypeSupportRule 繼承自 com.alibaba.p3c.pmd.lang.java.rule.AbstractAliRule ,AbstractAliRule繼承自AbstractJavaRule,重寫了setDescription,setMessage和addViolationWithMessage等方法,這裏提到的3個方法,增長了多語言支持。p3c-pmd使用Resource Bundle來提供多語言支持。每一個消息都有一個惟一id來對應,p3c-pmd經過重寫方法,將方法參數映射爲消息的id,以統一消息的配置。以下爲本地對應的消息提示內容

<!--extend-->
    <entry key="java.extend.AvoidFastJsonAutoTypeSupportRule.rule.msg">
        <![CDATA[不要打開fastjson的setAutoTypeSupport特性]]>
    </entry>
    <entry key="java.extend.AvoidFastJsonAutoTypeSupportRule.rule.desc">
        <![CDATA[
說明:fastjson的setAutoTypeSupport特性存在安全漏洞
        ]]>
    </entry>

 對於測試,按照1.2.4.的介紹,則有以下的文件路徑

file

文件內容比較簡單,這裏就不貼出來了。

 至此,已經完成了自定義規則的實現,如今就是要把該內容應用到ide上了。首先,須要將該模塊進行編譯,這裏直接保存到本地maven參考,好在本地調試。

 直接將p3c-pmd的版本升級爲2.0.1,而後執行mvn install,能夠在本地倉庫看到對應的版本

file

有了該版本,則能夠在其餘模塊引用該版本進行新功能調試,下面將以idea-plugin模塊爲例。

3. idea-plugin

 idea-plugin主要實現了idea的插件,可以對代碼進行實時檢查。這裏涉及到idea自定義插件的開發,這裏就不深刻了,網上有不少教程。這裏只介紹如何將上面自定義的規則接入該模塊。

1.修改idea-plugin模塊的build.gradle文件,開啓本地倉庫配置,以便從本地直接加載最新的p3c-pmd依賴。

buildscript {
    repositories {
        maven {
            url "https://oss.sonatype.org/content/repositories/snapshots/"
        }
        maven {
            url 'http://dl.bintray.com/jetbrains/intellij-plugin-service'
        }
        mavenLocal()
        mavenCentral()

    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

allprojects {
    group 'com.alibaba.p3c.idea'
    apply plugin: 'java'
    apply plugin: 'kotlin'
    apply plugin: 'maven-publish'

    sourceCompatibility = 1.8
    compileJava.options.encoding = 'UTF-8'
    configurations.all {
        resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
    }
    repositories {
        mavenLocal()
        jcenter()
        mavenCentral()
    }

    dependencies {
        compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
        testCompile group: 'junit', name: 'junit', version: '4.11'
    }
}

如上,增長了mavenLocal()

2.修改p3c-common的build.gradle,更改p3c-pmd的版本爲2.0.1

3.修改p3c-common模塊resources/rulesets/java/ali=pmd.xml,增長

<rule ref="rulesets/java/ali-extend.xml"/>

以增長自定義規則檢查。

4.在p3c-common模塊下,執行 gradle clean buildPlugin ,生成對應的插件。

4. 驗證

 本地安裝該插件,能夠獲得以下效果

file

file

 這裏只驗證效果,沒有真正引入fastjson依賴,也驗證了pmd檢查的是源碼文本。

更多原創內容請搜索微信公衆號:啊駝(doubaotaizi)
相關文章
相關標籤/搜索