系列文章目录
文章目录
- 系列文章目录
- rule 的应用
- 类别 rule
- rule 自定义
- XML rule 定义
- Tree 漫游
- 错误报告
- 生命周期
- designer
rule相关的代码在每个子 module 的 rule 文件夹。而且也以一些 ruleset 为范围分了文件夹,如下图所示:
对每个 rule 来说,定义检测规则并抛出错误,报错方法:addViolation
对应的 rule 相关 message 在 xml 中定义:
这里和 spotbugs 是一个思路,message 做 rule 代码与 xml 隔离,配置大于编码。rule 使用引擎调用,并利用 visitor 模式做行为与数据的解耦。整体思路是一脉相承的。
rule 的应用
首先,遍历规则集合rules中的每个规则rule。
- 检查当前语言版本的规则是否适用于当前的索引。如果不符合,则跳过该规则,不尝试应用它。
- 创建一个RuleContext对象ctx,用于存储规则应用时的上下文信息。
- 调用规则的start方法,开始应用规则。
- 使用TimedOperation对象rcto记录规则应用的时间。
- 获取规则的目标选择器的访问节点。
- 遍历目标选择器的访问节点,对每个节点应用规则。
- 记录应用规则的节点数量。
- 在应用规则的过程中,捕获并处理可能的运行时异常和栈溢出异常。
- 在应用规则之后,关闭规则应用的时间记录。
- 调用规则的end方法,表示规则应用完成。
private void applyOnIndex(TreeIndex idx, Collection<? extends Rule> rules, FileAnalysisListener listener) {
for (Rule rule : rules) {
if (!RuleSet.applies(rule, currentLangVer)) {
continue; // No point in even trying to apply the rule
}
RuleContext ctx = RuleContext.create(listener, rule);
rule.start(ctx);
try (TimedOperation rcto = TimeTracker.startOperation(TimedOperationCategory.RULE, rule.getName())) {
int nodeCounter = 0;
Iterator<? extends Node> targets = rule.getTargetSelector().getVisitedNodes(idx);
while (targets.hasNext()) {
Node node = targets.next();
try {
nodeCounter++;
rule.apply(node, ctx);
} catch (RuntimeException e) {
reportOrRethrow(listener, rule, node, AssertionUtil.contexted(e), true);
} catch (StackOverflowError e) {
reportOrRethrow(listener, rule, node, AssertionUtil.contexted(e), SystemProps.isErrorRecoveryMode());
} catch (AssertionError e) {
reportOrRethrow(listener, rule, node, AssertionUtil.contexted(e), SystemProps.isErrorRecoveryMode());
}
}
rcto.close(nodeCounter);
} finally {
rule.end(ctx);
}
}
}
类别 rule
和 spotbugs 的能到某些行等的不同,AST 的建立是以元数据为中心,
visit 的类型有如下这些,共 122 个类别:
public default R visitJavaNode(JavaNode node, P data) { return visitNode(node, data); }
default R visit(ASTCompilationUnit node, P data) { return visitJavaNode(node, data); }
default R visit(ASTPackageDeclaration node, P data) { return visitJavaNode(node, data); }
default R visit(ASTImportDeclaration node, P data) { return visitJavaNode(node, data); }
default R visit(ASTModifierList node, P data) { return visitJavaNode(node, data); }
default R visit(ASTClassOrInterfaceDeclaration node, P data) { return visitJavaNode(node, data); }
default R visit(ASTExtendsList node, P data) { return visitJavaNode(node, data); }
default R visit(ASTImplementsList node, P data) { return visitJavaNode(node, data); }
default R visit(ASTPermitsList node, P data) { return visitJavaNode(node, data); }
default R visit(ASTEnumDeclaration node, P data) { return visitJavaNode(node, data); }
default R visit(ASTEnumBody node, P data) { return visitJavaNode(node, data); }
default R visit(ASTEnumConstant node, P data) { return visitJavaNode(node, data); }
default R visit(ASTRecordDeclaration node, P data) { return visitJavaNode(node, data); }
default R visit(ASTRecordComponentList node, P data) { return visitJavaNode(node, data); }
default R visit(ASTRecordComponent node, P data) { return visitJavaNode(node, data); }
default R visit(ASTRecordBody node, P data) { return visitJavaNode(node, data); }
default R visit(ASTCompactConstructorDeclaration node, P data) { return visitJavaNode(node, data); }
default R visit(ASTTypeParameters node, P data) { return visitJavaNode(node, data); }
default R visit(ASTTypeParameter node, P data) { return visitJavaNode(node, data); }
default R visit(ASTClassOrInterfaceBody node, P data) { return visitJavaNode(node, data); }
default R visit(ASTEmptyDeclaration node, P data) { return visitJavaNode(node, data); }
default R visit(ASTFieldDeclaration node, P data) { return visitJavaNode(node, data); }
default R visit(ASTVariableDeclarator node, P data) { return visitJavaNode(node, data); }
default R visit(ASTVariableDeclaratorId node, P data) { return visitJavaNode(node, data); }
default R visit(ASTReceiverParameter node, P data) { return visitJavaNode(node, data); }
default R visit(ASTArrayInitializer node, P data) { return visitJavaNode(node, data); }
default R visit(ASTMethodDeclaration node, P data) { return visitJavaNode(node, data); }
default R visit(ASTFormalParameters node, P data) { return visitJavaNode(node, data); }
default R visit(ASTFormalParameter node, P data) { return visitJavaNode(node, data); }
default R visit(ASTArrayType node, P data) { return visitJavaNode(node, data); }
default R visit(ASTArrayDimensions node, P data) { return visitJavaNode(node, data); }
default R visit(ASTArrayTypeDim node, P data) { return visitJavaNode(node, data); }
default R visit(ASTConstructorDeclaration node, P data) { return visitJavaNode(node, data); }
default R visit(ASTBlock node, P data) { return visitJavaNode(node, data); }
default R visit(ASTExplicitConstructorInvocation node, P data) { return visitJavaNode(node, data); }
default R visit(ASTInitializer node, P data) { return visitJavaNode(node, data); }
default R visit(ASTIntersectionType node, P data) { return visitJavaNode(node, data); }
default R visit(ASTClassOrInterfaceType node, P data) { return visitJavaNode(node, data); }
default R visit(ASTTypeArguments node, P data) { return visitJavaNode(node, data); }
default R visit(ASTWildcardType node, P data) { return visitJavaNode(node, data); }
default R visit(ASTPrimitiveType node, P data) { return visitJavaNode(node, data); }
default R visit(ASTVoidType node, P data) { return visitJavaNode(node, data); }
default R visit(ASTThrowsList node, P data) { return visitJavaNode(node, data); }
default R visit(ASTAssignmentExpression node, P data) { return visitJavaNode(node, data); }
default R visit(ASTConditionalExpression node, P data) { return visitJavaNode(node, data); }
default R visit(ASTInfixExpression node, P data) { return visitJavaNode(node, data); }
default R visit(ASTTypePattern node, P data) { return visitJavaNode(node, data); }
default R visit(ASTRecordPattern node, P data) { return visitJavaNode(node, data); }
default R visit(ASTComponentPatternList node, P data) { return visitJavaNode(node, data); }
default R visit(ASTUnaryExpression node, P data) { return visitJavaNode(node, data); }
default R visit(ASTCastExpression node, P data) { return visitJavaNode(node, data); }
default R visit(ASTSwitchExpression node, P data) { return visitJavaNode(node, data); }
default R visit(ASTThisExpression node, P data) { return visitJavaNode(node, data); }
default R visit(ASTSuperExpression node, P data) { return visitJavaNode(node, data); }
default R visit(ASTClassLiteral node, P data) { return visitJavaNode(node, data); }
default R visit(ASTMethodCall node, P data) { return visitJavaNode(node, data); }
default R visit(ASTArrayAccess node, P data) { return visitJavaNode(node, data); }
default R visit(ASTFieldAccess node, P data) { return visitJavaNode(node, data); }
default R visit(ASTMethodReference node, P data) { return visitJavaNode(node, data); }
default R visit(ASTLambdaExpression node, P data) { return visitJavaNode(node, data); }
default R visit(ASTLambdaParameterList node, P data) { return visitJavaNode(node, data); }
default R visit(ASTLambdaParameter node, P data) { return visitJavaNode(node, data); }
default R visit(ASTBooleanLiteral node, P data) { return visitJavaNode(node, data); }
default R visit(ASTNullLiteral node, P data) { return visitJavaNode(node, data); }
default R visit(ASTNumericLiteral node, P data) { return visitJavaNode(node, data); }
default R visit(ASTCharLiteral node, P data) { return visitJavaNode(node, data); }
default R visit(ASTStringLiteral node, P data) { return visitJavaNode(node, data); }
default R visit(ASTArgumentList node, P data) { return visitJavaNode(node, data); }
default R visit(ASTConstructorCall node, P data) { return visitJavaNode(node, data); }
default R visit(ASTAnonymousClassDeclaration node, P data) { return visitJavaNode(node, data); }
default R visit(ASTArrayAllocation node, P data) { return visitJavaNode(node, data); }
default R visit(ASTArrayDimExpr node, P data) { return visitJavaNode(node, data); }
default R visit(ASTExpressionStatement node, P data) { return visitJavaNode(node, data); }
default R visit(ASTLabeledStatement node, P data) { return visitJavaNode(node, data); }
default R visit(ASTLocalVariableDeclaration node, P data) { return visitJavaNode(node, data); }
default R visit(ASTEmptyStatement node, P data) { return visitJavaNode(node, data); }
default R visit(ASTSwitchStatement node, P data) { return visitJavaNode(node, data); }
default R visit(ASTSwitchArrowBranch node, P data) { return visitJavaNode(node, data); }
default R visit(ASTSwitchFallthroughBranch node, P data) { return visitJavaNode(node, data); }
default R visit(ASTSwitchLabel node, P data) { return visitJavaNode(node, data); }
default R visit(ASTSwitchGuard node, P data) { return visitJavaNode(node, data); }
default R visit(ASTYieldStatement node, P data) { return visitJavaNode(node, data); }
default R visit(ASTIfStatement node, P data) { return visitJavaNode(node, data); }
default R visit(ASTWhileStatement node, P data) { return visitJavaNode(node, data); }
default R visit(ASTDoStatement node, P data) { return visitJavaNode(node, data); }
default R visit(ASTForeachStatement node, P data) { return visitJavaNode(node, data); }
default R visit(ASTForStatement node, P data) { return visitJavaNode(node, data); }
default R visit(ASTForInit node, P data) { return visitJavaNode(node, data); }
default R visit(ASTStatementExpressionList node, P data) { return visitJavaNode(node, data); }
default R visit(ASTForUpdate node, P data) { return visitJavaNode(node, data); }
default R visit(ASTBreakStatement node, P data) { return visitJavaNode(node, data); }
default R visit(ASTContinueStatement node, P data) { return visitJavaNode(node, data); }
default R visit(ASTReturnStatement node, P data) { return visitJavaNode(node, data); }
default R visit(ASTThrowStatement node, P data) { return visitJavaNode(node, data); }
default R visit(ASTSynchronizedStatement node, P data) { return visitJavaNode(node, data); }
default R visit(ASTTryStatement node, P data) { return visitJavaNode(node, data); }
default R visit(ASTResourceList node, P data) { return visitJavaNode(node, data); }
default R visit(ASTResource node, P data) { return visitJavaNode(node, data); }
default R visit(ASTCatchClause node, P data) { return visitJavaNode(node, data); }
default R visit(ASTCatchParameter node, P data) { return visitJavaNode(node, data); }
default R visit(ASTUnionType node, P data) { return visitJavaNode(node, data); }
default R visit(ASTFinallyClause node, P data) { return visitJavaNode(node, data); }
default R visit(ASTAssertStatement node, P data) { return visitJavaNode(node, data); }
default R visit(ASTAnnotation node, P data) { return visitJavaNode(node, data); }
default R visit(ASTAnnotationMemberList node, P data) { return visitJavaNode(node, data); }
default R visit(ASTMemberValuePair node, P data) { return visitJavaNode(node, data); }
default R visit(ASTMemberValueArrayInitializer node, P data) { return visitJavaNode(node, data); }
default R visit(ASTAnnotationTypeDeclaration node, P data) { return visitJavaNode(node, data); }
default R visit(ASTAnnotationTypeBody node, P data) { return visitJavaNode(node, data); }
default R visit(ASTDefaultValue node, P data) { return visitJavaNode(node, data); }
default R visit(ASTModuleDeclaration node, P data) { return visitJavaNode(node, data); }
default R visit(ASTModuleRequiresDirective node, P data) { return visitJavaNode(node, data); }
default R visit(ASTModuleExportsDirective node, P data) { return visitJavaNode(node, data); }
default R visit(ASTModuleOpensDirective node, P data) { return visitJavaNode(node, data); }
default R visit(ASTModuleUsesDirective node, P data) { return visitJavaNode(node, data); }
default R visit(ASTModuleProvidesDirective node, P data) { return visitJavaNode(node, data); }
default R visit(ASTModuleName node, P data) { return visitJavaNode(node, data); }
default R visit(ASTAmbiguousName node, P data) { return visitJavaNode(node, data); }
default R visit(ASTVariableAccess node, P data) { return visitJavaNode(node, data); }
default R visit(ASTTypeExpression node, P data) { return visitJavaNode(node, data); }
default R visit(ASTPatternExpression node, P data) { return visitJavaNode(node, data); }
default R visit(ASTLocalClassStatement node, P data) { return visitJavaNode(node, data); }
rule 自定义
PMD 是基于 AST的检测框架,语法树表示了源代码中的语法与语义特征。 Rule 遍历 AST 并匹配某些条件。
自定义规则有2种方式: Xpath query 和 Java visitor。 Xpath 直接使用 XML 定义 更简单,但是有一些只能通过 Java 调用 PMD api 的实现,是Xpath无法达到的。
XML rule 定义
新的规则必须在 ruleset 中定义,并使用 class 属性,引用规则的实现。
java rule:
<rule name="MyJavaRule"
language="java"
message="Violation!"
class="com.me.MyJavaRule">
<description>
Description
</description>
<priority>3</priority>
</rule>
xpath:
<rule name="MyXPathRule"
language="java"
message="Violation!"
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule">
<description>
Description
</description>
<priority>3</priority>
<properties>
<property name="xpath">
<value><![CDATA[
//ClassOrInterfaceDeclaration
]]></value>
</property>
</properties>
</rule>
要写 Java rule,需要完成如下
- 编写一个实现接口Rule的Java类。每种语言实现都提供了一个基本规则类来缓解您的痛苦,例如AbstractJavaRule。
- 编译此类,将其链接到PMD API(例如,使用PMD作为Maven依赖项)
- 将其打包到JAR中,并将其添加到PMD的执行类路径中
- 在规则集XML中声明规则
Tree 漫游
开始应用 rule 的时候,会遍历整棵树来寻找错误,每个 rule 都定义了一个特别的 visit 方法。可以根据需要在 visitor 中选择需要的节点 override 该方法。
public class MyRule extends AbstractJavaRule {
@Override
public Object visit(ASTVariableId node, Object data) {
// This method is called on each node of type ASTVariableId
// in the AST
if (node.getType() == short.class) {
// reports a violation at the position of the node
// the "data" parameter is a context object handed to by your rule
// the message for the violation is the message defined in the rule declaration XML element
asCtx(data).addViolation(node);
}
// this calls back to the default implementation, which recurses further down the subtree
return super.visit(node, data);
}
}
super.visit(node, data) 调用在规则实现中非常常见,因为它通过访问当前节点的所有子代来继续遍历。如果不需要再往子节点遍历,可以不用再调用 super 的方法。
一个加速手段是使用 rulechain。条件是 rule 不需要维护 visit 之间的状态,这类规则也很常见:
rulechain 不会递归整棵树,而是只遍历它关心的部分
- 必须 override buildTargetSelector 方法,工程方法 forType 能用例创建这样的选择器。
- 继承 AbstractJavaRulechainRule. 调用父类的构造器,并提供所需要的类型
- visit 方法不能递归,不能调用 super.visit
PMD 提供了2中 AST 导航的方式:NodeStream 和 Node api:
ancestors
ancestorsOrSelf
children
descendants
descendantsOrSelf
ancestors
children
descendants
NodeStream.of(someNode) // the stream here is empty if the node is null
.filterIs(ASTVariableDeclaratorId.class)// the stream here is empty if the node was not a variable declarator id
.followingSiblings() // the stream here contains only the siblings, not the original node
.filterIs(ASTVariableInitializer.class)
.children(ASTExpression.class)
.children(ASTPrimaryExpression.class)
.children(ASTPrimaryPrefix.class)
.children(ASTLiteral.class)
.filterMatching(Node::getImage, "0")
.filterNot(ASTLiteral::isStringLiteral)
.nonEmpty(); // If the stream is non empty here, then all the pipeline matched
Node api:
getParent
getNumChildren
getChild
getFirstChild
getLastChild
getPreviousSibling
getNextSibling
firstChild
错误报告
addViolation
addViolationWithMessage 这个方法不会使用 xml 中定义的 message。
生命周期
对于每个线程,规则都深拷贝一份,而且每个线程分析的不同的文件集。一个rule 的生命周期,比较简单:
- start 调用依次,parsing 之前
- apply 调用 visit 方法
- end 结束文件处理
designer
若缺少 javafx 框架,下载地址: https://openjfx.cn/dl/
并在环境变量里配置 JAVAFX_HOME
使用命令调用:
pmd.bat designer
双击代码行,定位到导航树:
GUI 相关的比较简单,大家可以自行探索。