PMD分析源码过程简介

1.    PMD简介

PMD是一款采用BSD协议发布的Java程序代码检查工具。该工具可以做到检查Java代码中是否含有未使用的变量、是否含有空的抓取块、是否含有不必要的对象等。该软件功能强大,扫描效率高,是Java程序员debug的好帮手。

PMD是扫描 Java 源码并查找以下潜在问题:

空捕捉块(catch block

  • 从未用过的参数
  • if声明
  • 重复的导入声明
  • 从未用过的私有方法
  • 孤立的类
  • 短型或长型变量及方法名

PMD 具有下列软件的插件:JEdit, JBuilder, NetBeans/Sun ONE Studio, IntelliJ IDEA, TextPad, Maven, Ant, Eclipse, Gel, Emacs

1.1          PMD 的含义

坦率地说,我们其实也不知道PMD的真正意义所在 (我们只是认为这三个字母拼在一起较为好听) 。但是,我们提出了计算行业的几种解释以供参考。

PMD:

  • Pretty Much Done  (几乎无所不能)
  • Project Mess Detector  (项目故障探测器)
  • Project Monitoring Directives  (项目监视器)
  • Protein Mutant Database  (基因突变数据库)
  • Project Meets Deadline  (项目到期)
  • Programming Mistake Detector  (程序错误检测器)
  • Pounds Mistakes Dead  (彻底纠错)
  • PMD Meaning Discovery  PMD含义探索)

1.2          PMD运行机制

PMD 根据规则核对源码并产生一个报告。具体如下:

  • 有文件名和RuleSet传入PMD
  • PMD将通过该文件的InputStream传递给由JavaCC-生成的解析器;
  • PMD 从解析器取得指向抽象语法树(AST)的引用;
  • RuleSet 中的每个规则都遍历AST 并检查错误;
  • 报告内容包括RuleViolations以及符合XML HTML 或其它格式的代码/文件;

1.3          PMD用法

PMD之后有几个选项:

Generate reports:生成报告,生成在你的工程目录下的reports里;

Clear violation reviews

Find suspect Cut And Paste..:是检查指种后缀的文件。如JAVAJSP等;

Check Code With PMD:就是检查代码。

做好以上步骤以后就会在项目中静态的检测你每一行的代码,如果有不符合要求的会出现警告和错误信息。

2.    集成PMD至规则插件

NC规则检查工具原来的代码检查使用的是JavaParser,但是JavaParser毕竟功能上比较弱,能够分析出来的结果不够全面,而且最近一段时间没有更新,仅支持Java1.5

因此,考虑到有些部门提出的规则对代码检查要求比较复杂,JavaParser很难进行编程处理,考虑集成PMD中的源码,改造其API入口方法,使其能够在实际的规则中使用,并能够以PMD规则的方式对其进行扩展。

集成后的PMD功能包括:

l  仅保留支持Java1.7代码的检查,将其他文件(xmlhtmljspcpp等)的支持抛弃;

l  去除用于代码检查时进行性能测试的Benchmark功能;

l  仍然保持了整个规则执行的整理流程,去掉了原有规则集合(RuleSetFactory, RuleSet)的概念,仅支持单个规则的检查;

l  保留了PMD中原有的各类规则;

l  去除了用于生成报告的过程;

 

最后进行规则检查只需要执行SourceCodeProcessor中的如下方法即可:

         /**

          * 解析规则文件,得出解析结果上下文

          * @param filePath -源代码文件路径

          * @param rule -要执行的规则实现

          * @return

          * @throws FileNotFoundException

          */

         publicstatic RuleContext parseRule(String filePath, Rule rule) throws FileNotFoundException{

 

         其中,用户需要做的事情就是执行规则检查时,扩展Rule接口,其中扩展部分内容见下节。

         用户使用pmd工具需要事先依赖pmdbundlecom.yonyou.nc.codevalidator.code.pmdadaptor

3.    PMD扩展

在介绍完PMD的使用后,下面重点介绍该如何对PMD进行扩展,如何编写自己的规则用在代码检查上。

 

3.1          规则Rule类结构

 

        

 

规则的最基础接口是net.sourceforge.pmd.Rule,其中有一系列的抽象类,下面主要介绍几个重要的抽象类:

AbstractStatisticJavaRule:顾名思义,该抽象类主要就是用于处理一些统计规则的实现,比如计算方法体中代码行数超出一定范围,过多的方法参数,过长的class文件等。如果用户需要计算一些统计信息,可以从此类中派生。

AbstractJavaRule: 一般的Java源码分析规则都是从本类中继承,包括前面所说的统计规则;当前其中也包括一些包含某些特殊帮助方法的规则抽象类,用户可参考其实现。总之,用户如果没有找到特殊适合的实现类型,那么从该类中继承并实现规则。

 

3.2          Java NodeVisitor访问

PMD中的源码分析与JavaParser大体类似,都是将整个Java文件分析成各种子Node,且Node之间存在着Composite关系,采用访问者模式(Visitor模式,参考设计模式),这样就可以在解析Java文件的时候,选择切入点,关注感兴趣的具体Node实现(后面详细介绍Node的结构)。

 

 

 

 

3.3          Java Node的结构

在进行Java代码检查的时候,经常会使用的便是PMD中提供的JavaNode结构,只有详细了解了JavaNode的结构,才能够有针对性的编写检查代码中特定问题的规则。

编译单元(.java)分析

下面就从最顶层的一个编译单元说起,详细介绍一下JavaNode中的结构,一个编译单元就是对应一个.java文件(其中可能包含多个类,包括内部类)。

 

 

         ASTCompilationUnit包括三个基本部分:ASTPackageDeclaration(包声明),ASTImportDeclaration(引用其他类声明)ASTTypeDeclaration类型声明。

         其中包声明和引用列表声明中引用的类都以ASTName形式存在;

         如果类中包含非内部类,会存在多个ASTTypeDeclaration

类型声明分析

         对于具体的类型声明,包括ASTExtendsList(继承的类,java中只能继承单个)ASTImplementsList(实现的接口,可多个)和ASTClassOrInterfaceBody(实现类或接口体)。

 

 

         当类型中存在着内部类时(不区分静态或非静态内部类),ASTClassOrInterfaceBody中会包含多个ASTClassOrInterfaceBodyDeclaration,其中内部类的子仍然聚合了对应的ASTClassOrInterfaceDeclaration,主类向下延伸,见下文。

具体类型元素分析

具体的类型实现体中,包含下面的内容:

ASTFieldDeclaration:字段声明,其中有着ASTType字段的类型(根据情况分为基本类型ASTPrimaryType和引用类型ASTReferenceType),以及ASTVariableDeclarator字段变量的定义。

ASTConstructorDeclaration:构造方法声明,其中包括ASTFormalParameters构造函数参数和对应ASTBlockStatement构造函数语句

ASTMethodDeclaration:方法声明,包含ASTReturnType返回值定义,ASTMethodDeclarator方法定义(其中可包含多个方法参数),ASTNameList可能抛出的异常列表,ASTBlock方法体。

 

 

         实现AccessNode接口的Node(都是从AbstractJavaAccessNode中继承),其中都有着访问符(modifiers)API,即可以访问该字段或方法声明中修饰符(static, final, private等)。

 

 

         而对于类似字段声明的部门,都存在着一颗类似上图中的结构(ASTFieldDeclaration结构)。

常见方法内语句结构图

下面介绍一下比较常见的方法内语句结构图(do whilefor循环类似,暂不介绍):

try语句

         try语句statement包含ASTBlocktry块中的内容),ASTCatchStatementcatch块中的内容),ASTFinallyStatementfinally语句中的内容),其中catchfinally块递归也包含相应ASTBlockASTBlockStatement

 

        

 

If语句

 

 

解析完成的结构:

         其中,第一个ASTExpression为判断语句,第二个ASTStatementif内部体,第三个ASTStatement中又包含了一个ASTIf语句,此为else子句,以此类推,最后的else嵌入在ASTStatement中。

 

 


 至于其他类型分支,分析过程都比较类似,这里就不一一介绍了。

3.4          规则编写常用的方法

编写规则需要直接从net.sourceforge.pmd.lang.java.rule.AbstractJavaRule中(或其子类)继承,严格遵守Visitor模式(参考设计模式),仅关注需要实现的方法。

可以参考pmd中内置的规则实现,写出自己的规则,内置的规则实现在bundle/com.yonyou.nc.codevalidator.code.pmdadaptor中,net.sourceforge.pmd.lang.java.rule包下,已按照规则的分类和使用情况分成不同的实现包进行管理,并在每个包中都有一个对应的xml文件用于描述本包中的规则意义。

 

 

 

         此外,还有一些常用的方法,用来在规则中使用,没介绍的方法,自己查阅,有问题沟通。

 

String net.sourceforge.pmd.lang.ast.Node.getImage()

在某些类型的节点中,用来获得定义的方法名,变量名,参数名等名称类信息,具体请查看相应的定义;

 

<T> List<T> net.sourceforge.pmd.lang.ast.Node.findDescendantsOfType(Class<T> targetType)

在某个节点上,查找其子节点中是否包含某个特定类型的子节点(递归向下查询)。

 

<T> List<T> net.sourceforge.pmd.lang.ast.AbstractNode.getParentsOfType(Class<T> parentType)

在某个节点上,查找其父节点是否包含某个特定类型(递归向上查询)。

 

Node net.sourceforge.pmd.lang.ast.Node.jjtGetChild(int index)

得到节点的子节点,可通过node.jjtGetNumChildren()获取子节点的数量来协助工作。

 

结束

最后,由于项目中用到了PMD中的分析过程,而剥离掉原有的例如报表生成等功能,抽象出了一个简单可轻易调用的类库及其源码,具体信息请参考github:

https://github.com/clamaa/pmd-adaptor

1.    PMD简介 空捕捉块( catch block
  • 从未用过的参数
  • if声明
  • 重复的导入声明
  • 从未用过的私有方法
  • 孤立的类
  • 短型或长型变量及方法名

猜你喜欢

转载自brandnewuser.iteye.com/blog/2017596
PMD