PMD 开发自定义 XPath 规则解析 lambda 的 AST

[TOC]

概述

最近想自定义 PMD 规则来检测java代码,发现 XPath 挺好用的,刚好也解析出来了一个 lambda 表达式的校验,所以分享一下

0x1 概念介绍

AST

根据 PMD规则介绍得知

image.png

大概意思就是:

在运行规则之前,PMD 会把源文件格式化为 AST (抽象语法树) 。 这个技术很多地方用到了,如 Eclipse 、 IDEA、JavaParser) 。这棵树可以体现出来代码的语法结构, 并且比源码的信息还要丰富和规范,所以 PMD 是直接拿着规则去匹配 AST

XPath

AST 在 PMD 中被解析出来之后,格式是 xml 的,想去搜索里面的信息,就要用到 XPath 的技术
其实就是一种搜索 xml 元素的技术,类似的技术还有 JQuery 搜索 html

语法介绍在这里 XPath语法

大概看一下 // / [] 就行,其它的用到再看

0x2 开发

pmd 支持的有两种

  • XPath
  • Java Visit

XPath 的配置规则的话,需要看的文档少,上手快,我选的就是这个
不过文档从哪里看起呢?先不着急,看看别人是怎么玩的

p3c-pmd

阿里p3c

阿里内置了很多规则,有的就是用 XPath 实现的,我们挑一个看看

AvoidPatternCompileInMethodRule

代码截图


image.png

代码的作用是: 不能在方法体内部出现 Pattern.compile("xx"),因为正则表达式编译很消耗性能,应该在常量里做这个步骤

我们可以看到规则的实现代码很简单,其中红框部分就是检测代码的规则
我抠出来格式化一下

//MethodDeclaration//PrimaryExpression
  [PrimaryPrefix/Name[@Image='Pattern.compile'] 
  and 
  PrimarySuffix/Arguments/ArgumentList/Expression
    /PrimaryExpression/PrimaryPrefix/Literal[@StringLiteral='true']]

Pattern.compile("xx")

逐一分析一下

//MethodDeclaration 从根节点开始,选择所有 MethodDeclaration

//PrimaryExpression 在所有的 MethodDeclaration 的下面,继续跳着选 PrimaryExpression

[到末尾] 筛选 PrimaryExpression 节点必须满足后续两个条件

  1. PrimaryPrefix/Name[@Image='Pattern.compile']
    PrimaryExpression 有一个子元素 PrimaryPrefix, 且 PrimaryPrefix 的子元素 name 值为 Pattern.compile
  2. PrimarySuffix/Arguments/ArgumentList/Expression/PrimaryExpression/PrimaryPrefix/Literal[@StringLiteral='true']
    PrimaryExpression 有一个子元素 PrimarySuffix,这个子元素有个子元素Arguments,这个子元素有个子元素 ArgumentList,一直套娃到 PrimaryPrefix, PrimaryPrefix的子元素 Literal 满足条件 @StringLiteral='true'

简单翻译过来就是,方法是 Pattern.compile, 这个方法有入参且是字符串

看一下语法树


image.png
  • 橙色是快速选择的 //
  • 红色是条件1
  • 蓝色是条件2

lambda

我们接下来研究一下 lambda 的 AST (注意:我是带着目的来研究特定场景、特定写法的 lambda 的,结论不一定适合所有场景)

我的场景是 避免工具类滥用

        B layer1 = Null.of(() -> a.getB());
        C layer2 = Null.of(() -> a.getB().getC());
        Integer layer3 = Null.of(() -> a.getB().getC().getNumber());

我们的工具类方法 Null.of 里面会对 NullPointerException 进行捕获,在捕获到的时候返回一个默认值
这个工具类主要是为了在处理获取嵌套字段的时候书写方便, 比如 Null.of(() -> a.getB().getC().getNumber()), 如果自己手写 if (null != xxx) 需要写很多层判断,代码会很恶心。
工具类的本意是好的,不过后面大家滥用了,在一层的时候也使用工具类。因为lambda表达式内部有可能会发生空指针异常,而发生异常时,因为jvm要停下来获取线程栈快照并填充到异常栈里,所以工具类性能较低,所以希望大家在一层的时候,自己手写。
好,背景介绍完了,现在要分析一下语法了,先看一下对比图

image.png

第二行相比第一行,代码多了.getc(), AST 多了两个 PrimarySuffix 元素
第三行相比第二行,代码多了.getNumber(), AST 多了两个 PrimarySuffix 元素

所以 lambda 的 AST 大概规律如下:


lambda-AST

所以我们要找的代码Null.of(() -> a.getB())规律就是: suffix元素有且仅有一个

(图是用 latex画的)latex公式

开发自定义规则

规则

根据 阿里的 p3c-pmd 照葫芦画瓢,可以得到 XPath 表达式

//MethodDeclaration//PrimaryExpression[
    PrimaryPrefix/Name[@Image='Null.of']
    and
    PrimarySuffix/Arguments/ArgumentList/Expression/PrimaryExpression/PrimaryPrefix/LambdaExpression
       /Expression/PrimaryExpression[count(PrimarySuffix)=1]
]

核心就是 PrimaryExpression[count(PrimarySuffix)=1]

开发

Java 代码的话,理解都不用理解,直接把 阿里的 AvoidPatternCompileInMethodRule 文件内容复制一下,然后改个名字,改一下 XPath, 改一下提示信息就可以了

code

还需要配置 规则xml, 简单的放在 阿里的文件里面就行,很简单就不赘述了
打包之后,运行

image.png

大功告成

参考

PMD规则
PMD rule-designer
XPath语法
阿里p3c

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。