溫馨提示×

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

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何使用p3c實(shí)現(xiàn)自定義代碼規(guī)范檢查

發(fā)布時(shí)間:2021-09-14 10:50:56 來(lái)源:億速云 閱讀:183 作者:柒染 欄目:大數(shù)據(jù)

這篇文章將為大家詳細(xì)講解有關(guān)如何使用p3c實(shí)現(xiàn)自定義代碼規(guī)范檢查,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。

p3c,一款代碼規(guī)范的檢查工具,有對(duì)應(yīng)的ide插件,能在編碼過(guò)程中對(duì)設(shè)置的規(guī)則進(jìn)行提示,便打算對(duì)它進(jìn)行拓展,增加對(duì)fastjson檢查是否打開(kāi)setAutoType特性的檢查。

?p3c主要包括3部分:

  • PMD實(shí)現(xiàn)(p3c-pmd):使用PMDhttps://pmd.github.io/來(lái)實(shí)現(xiàn)代碼規(guī)范檢查

  • Intellij IDEA插件

  • Eclipse插件

1. PMD

?p3c使用了PMD。PMD是一款靜態(tài)代碼掃描工具,該工具可以做到檢查Java代碼中是否含有未使用的變量、是否含有空的抓取塊、是否含有不必要的對(duì)象等。PMD使用JavaCC生成解析器來(lái)解析源代碼并生成AST(抽象語(yǔ)法樹(shù)),通過(guò)對(duì)AST的檢查可以直接從源代碼文本層面來(lái)對(duì)代碼進(jìn)行檢查,在PMD內(nèi)部稱為規(guī)則。即是否符合規(guī)則指的是,窮舉源碼各種可能的寫法,然后在AST上檢查是否出現(xiàn)。而規(guī)則的實(shí)現(xiàn),重點(diǎn)便在對(duì)AST的處理上。

1.1. AST

?關(guān)于AST的介紹網(wǎng)上有很多,可以直接搜索,這里重要提兩點(diǎn):

  • AST是源代碼的抽象語(yǔ)法結(jié)構(gòu)的樹(shù)狀表示

  • 抽象語(yǔ)法樹(shù)并不依賴于原語(yǔ)言的語(yǔ)法,也就是說(shuō)同語(yǔ)法分析階段所采用的上下文無(wú)關(guān)

?PMD使用JavaCC來(lái)生成AST。關(guān)于JavaCC也可以在網(wǎng)上查看相關(guān)資料,這里不多介紹,只要知道JavaCC是一個(gè)詞法分析生成器和語(yǔ)法分析生成器便行。

1.2. 自定義規(guī)則

?PMD官方文檔介紹了自定義規(guī)則的實(shí)現(xiàn)步驟,過(guò)程比較清晰,這里不贅述,只介紹下本文需要設(shè)計(jì)的步驟。

1.2.1. 定義規(guī)則集

?PMD的規(guī)則需要配置在XML文件中

  • 新建如下的空文件表示規(guī)則集

	<?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 ``` 標(biāo)簽中引用自定義規(guī)則

	```
	<rule ref="pathToYourOwnClass" />
	```

* 配置規(guī)則集	
	
###### 1.2.2. 配置規(guī)則

&emsp;可以在 ``` rule ``` 標(biāo)簽中對(duì)某一規(guī)則進(jìn)行配置

* 配置提示消息和告警級(jí)別

	```
	<rule ref="pathToYourOwnClass"
      message="message to show" >
      <priority>5</priority>
	</rule>
	```
	告警優(yōu)先級(jí)分為1-5個(gè)level,1最高,5最低
	
* 配置運(yùn)行參數(shù)

	可以通過(guò) ``` properties ``` 和 ``` propertie ```對(duì)類屬性賦值
	
###### 1.2.3. 編寫規(guī)則

&emsp;規(guī)則的編寫比較簡(jiǎn)單,PMD已經(jīng)給我們做好了配套的開(kāi)發(fā)框架和工具,只要確定后規(guī)則出現(xiàn)的情況,按照固定的模式去編寫即可。

* 確定實(shí)現(xiàn)方式

	可以使用純Java方式實(shí)現(xiàn),也可以使用XPath方式實(shí)現(xiàn)。
	
	對(duì)于純Java方式,PMD框架本身實(shí)現(xiàn)了對(duì)AST數(shù)的遍歷,用戶只要在遍歷各個(gè)節(jié)點(diǎn)的時(shí)候,對(duì)自定義規(guī)則的各種情況進(jìn)行分析判斷即可,過(guò)程類似與DOM文件的SAX解析,以流和事件的方式來(lái)解析AST內(nèi)容。
	
	對(duì)于XPath方式,則是將AST作為一個(gè)XML數(shù),以XPath的方式來(lái)遍歷解析內(nèi)容。
	
* 根據(jù)測(cè)試代碼確定可能出現(xiàn)的情況

	PMD自帶了一個(gè)designer,可以用來(lái)生成對(duì)應(yīng)代碼的AST內(nèi)容。對(duì)于本文需要達(dá)到的效果,涉及到如下代碼:
	
	```
	
	import com.alibaba.fastjson.parser.ParserConfig;

	public class NegativeExample {
	
		public static void main(String[] args) {
	        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
	    }
	}
	
	```
	
	在designer中可以得到如下的AST內(nèi)容
	
  ![file](https://oscimg.oschina.net/oscnet/491a18dd2086a3919b55fe84e4de1007549.jpg)
	
	具體內(nèi)容這里就不貼出來(lái)了,可以下載desigin然后貼上上面的代碼就會(huì)有。
	
	對(duì)于本文的需求,需要確定在有import ``` ParserConfig ``` 類的時(shí)候,調(diào)用了 ``` setAutoTypeSupport ``` 方法且參數(shù)為 ``` true ```。當(dāng)然這個(gè)條件是不夠嚴(yán)謹(jǐn)?shù)?,還需要判斷是否調(diào)用了 ``` ParserConfig ```,也要考慮有通過(guò)import導(dǎo)入直接寫全限定名的。但考慮到一般的寫法,以及ide的格式化處理,這樣處理已經(jīng)可以滿足大部分場(chǎng)景了。
	
* 編寫規(guī)則實(shí)現(xiàn)類

	確定了規(guī)則的判斷條件,就可以著手寫代碼了。
	
	對(duì)于Java方式,可以直接繼承 ```net.sourceforge.pmd.lang.java.rule.AbstractJavaRule ``` 類,然后重寫各種節(jié)點(diǎn)的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);
    }
	```

	對(duì)于XPath方式,可以通過(guò)繼承 ``` net.sourceforge.pmd.lang.rule.XPathRule ``` 類,重寫 ``` setXPath ``` 方法設(shè)定對(duì)應(yīng)的XPath即可。上面提到過(guò),可以通過(guò) ``` property ``` 配置對(duì)類的屬性進(jìn)行賦值,因而,對(duì)于XPat方式,可以在xml配置中進(jìn)行如下設(shè)置來(lái)啟用XPath。
	
	```
	<rule name="My Rule"
          language="java"
          message="violation message"
          class="net.sourceforge.pmd.lang.rule.XPathRule">
        <description>
Rule Description
         </description>
         <priority>3</priority>
         <properties>
             <property name="xpath">
                 <value>
                 <![CDATA[
--- here comes your XPath expression
]]>
				 </value>
             </property>
         </properties>
	```

###### 1.2.4. 測(cè)試規(guī)則

&emsp;PMD推薦對(duì)于每個(gè)規(guī)則,至少要有一個(gè)正向和逆向的測(cè)試用例,來(lái)驗(yàn)證規(guī)則出現(xiàn)和不出現(xiàn)的情況。對(duì)于規(guī)則的測(cè)試,PMD也提供了一套框架,只要按照約定好的方式添加xml測(cè)試文件即可。

&emsp;PMD約定了幾個(gè)規(guī)則,用來(lái)加載測(cè)試案例

* 測(cè)試類要繼承 ``` net.sourceforge.pmd.testframework.PmdRuleTst ```類,該整合了Junt,可以在里面增加需要的測(cè)試方法。

* 對(duì)于在 ``` src/test/resource ``` 和測(cè)試類對(duì)應(yīng)的路徑下增加一個(gè)xml目錄,在增加同第一步同名的xml文件,該文件用于書寫測(cè)試集。

	例如官網(wǎng)給出的例子

	規(guī)則實(shí)現(xiàn)類路徑如下:

	```
net.sourceforge.pmd.lang.java.rule.bestpractices.AbstractClassWithoutAbstractMethodTest
		
	```
	
	測(cè)試案例集如下:
	
	```
	src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AbstractClassWithoutAbstractMethod.xml
	
	```
	
	xml的規(guī)則則可以參考官網(wǎng)介紹,這里不再贅述。

#### 2. p3c-pmd

&emsp;代碼規(guī)范實(shí)現(xiàn)的主要模塊,使用pmd來(lái)實(shí)現(xiàn)。p3c-pmd模塊在代碼組織上很工整,可以按照相同的模式增加自定義的規(guī)則/規(guī)則集。

&emsp;對(duì)于本文需求,打算在該模塊的基礎(chǔ)上增加一個(gè)extend模塊,用于實(shí)現(xiàn)自定義規(guī)則集。如下,為對(duì)應(yīng)的源碼路徑好規(guī)則集路徑

![file](https://oscimg.oschina.net/oscnet/3e3f8a123f38290ab7992a2ac7db6e7d9d0.jpg)

![file](https://oscimg.oschina.net/oscnet/2e451daf3afca20540f4802d6ae072b8c66.jpg)

&emsp;本文采用純Java方式實(shí)現(xiàn)規(guī)則,按照1.2.3.節(jié)的描述,可以得到如下的實(shí)現(xiàn)類

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);
}

}

&emsp;對(duì)應(yīng)規(guī)則集中的配置為

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

&emsp;這里有幾點(diǎn)需要注意的,類 ``` AvoidFastJsonAutoTypeSupportRule ``` 繼承自 ``` com.alibaba.p3c.pmd.lang.java.rule.AbstractAliRule ```,AbstractAliRule繼承自AbstractJavaRule,重寫了setDescription,setMessage和addViolationWithMessage等方法,這里提到的3個(gè)方法,增加了多語(yǔ)言支持。p3c-pmd使用Resource Bundle來(lái)提供多語(yǔ)言支持。每個(gè)消息都有一個(gè)唯一id來(lái)對(duì)應(yīng),p3c-pmd通過(guò)重寫方法,將方法參數(shù)映射為消息的id,以統(tǒng)一消息的配置。如下為本地對(duì)應(yīng)的消息提示內(nèi)容
<!--extend-->
<entry key="java.extend.AvoidFastJsonAutoTypeSupportRule.rule.msg">
    <![CDATA[不要打開(kāi)fastjson的setAutoTypeSupport特性]]>
</entry>
<entry key="java.extend.AvoidFastJsonAutoTypeSupportRule.rule.desc">
    <![CDATA[

說(shuō)明:fastjson的setAutoTypeSupport特性存在安全漏洞 ]]> </entry>

&emsp;對(duì)于測(cè)試,按照1.2.4.的介紹,則有如下的文件路徑

![file](https://oscimg.oschina.net/oscnet/67322e2edc5dcc7d1e8148149930404a64d.jpg)

文件內(nèi)容比較簡(jiǎn)單,這里就不貼出來(lái)了。

&emsp;至此,已經(jīng)完成了自定義規(guī)則的實(shí)現(xiàn),現(xiàn)在就是要把該內(nèi)容應(yīng)用到ide上了。首先,需要將該模塊進(jìn)行編譯,這里直接保存到本地maven參考,好在本地調(diào)試。

&emsp;直接將p3c-pmd的版本升級(jí)為2.0.1,然后執(zhí)行mvn install,可以在本地倉(cāng)庫(kù)看到對(duì)應(yīng)的版本

![file](https://oscimg.oschina.net/oscnet/fe9ed36fd7ef30cb794e550782ee4d5d4c3.jpg)

有了該版本,則可以在其他模塊引用該版本進(jìn)行新功能調(diào)試,下面將以idea-plugin模塊為例。

#### 3. idea-plugin

&emsp;idea-plugin主要實(shí)現(xiàn)了idea的插件,能夠?qū)Υa進(jìn)行實(shí)時(shí)檢查。這里涉及到idea自定義插件的開(kāi)發(fā),這里就不深入了,網(wǎng)上有很多教程。這里只介紹如何將上面自定義的規(guī)則接入該模塊。

1.修改idea-plugin模塊的build.gradle文件,開(kāi)啟本地倉(cāng)庫(kù)配置,以便從本地直接加載最新的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"/> ```

以增加自定義規(guī)則檢查。

4.在p3c-common模塊下,執(zhí)行gradle clean buildPlugin,生成對(duì)應(yīng)的插件。

4. 驗(yàn)證

?本地安裝該插件,可以得到如下效果

如何使用p3c實(shí)現(xiàn)自定義代碼規(guī)范檢查

如何使用p3c實(shí)現(xiàn)自定義代碼規(guī)范檢查

這里只驗(yàn)證效果,沒(méi)有真正引入fastjson依賴,也驗(yàn)證了pmd檢查的是源碼文本。

關(guān)于如何使用p3c實(shí)現(xiàn)自定義代碼規(guī)范檢查就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

p3c
AI