- 静态分析
- 代码质量
【免费下载链接】pmd
An extensible multilanguage static code analyzer.
PMD 内置的pmd-xmlMaven 模块为 XML 及其衍生格式提供了完整的静态分析能力:它把 XML 文档的 DOM 直接暴露为 AST,让 PMD 规则与 CPD 重复代码检测可以作用于 XML、Maven POM、WSDL 与 XSL 四类文件。本文以官方文档 docs/pages/pmd/languages/xml.md 为骨架,结合pmd-xml模块的源码实现,完整讲解语言模块构成、--force-language文件归属机制,以及从XPathRule迁移到DomXPathRule的规则编写要点。读完本文,你将掌握如何用 PMD 分析非.xml扩展名的 XML 文件、如何写出符合 XPath 规范且可独立调试的 XML 规则。
XML 语言模块:将 DOM 暴露为 AST
PMD 的 XML 语言模块核心思路非常直接:把 XML 文档的 DOM 树直接当作 AST 来用,而不是像 Java 等语言那样先做语法解析再构建自定义节点。从源码看,这一设计贯穿整个模块:
- 解析入口 XmlParser.java 只是
XmlParserImpl的薄适配层; - XmlParserImpl.java 使用 JDK 内置的
DocumentBuilderFactory解析文本,得到标准的org.w3c.dom.Document; - AST 节点接口 XmlNode.java 定义为“对底层 DOM 节点的薄封装”,其实现 XmlNodeWrapper.java 通过
getNode()直接返回org.w3c.dom.Node。
由于 AST 就是 DOM,PMD 节点天生保留了 XML 的全部结构信息——包括注释、CDATA、处理指令(Processing Instruction)等在其他 AST 里容易被丢弃的节点。
四种语言变体
同一套解析设施之下,pmd-xml通过**独立的语言实例(language instance)**区分不同 XML 风味,每种实例都有自己的规则索引,并可能提供领域特定的 XPath 扩展函数。当前模块提供的语言如下表:
| Language ID | 说明 |
|---|---|
| xml | 通用 XML 语言 |
| pom | Maven Project Object Model(POM) |
| wsdl | Web Services Description Language(WSDL) |
| xsl | Extensible Stylesheet Language(XSL) |
从 PMD 7.13.0 起,POM、WSDL、XSL 在实现上被重构为XML 的方言(dialect),而不是独立的语言模块。这一点在服务注册文件 net.sourceforge.pmd.lang.Language 中看得一清二楚:
net.sourceforge.pmd.lang.xml.XmlLanguageModule net.sourceforge.pmd.lang.xml.xsl.XslDialectModule net.sourceforge.pmd.lang.xml.wsdl.WsdlDialectModule net.sourceforge.pmd.lang.xml.pom.PomDialectModule对应的方言模块(如 PomDialectModule.java、WsdlDialectModule.java、XslDialectModule.java)都通过asDialectOf("xml")声明自己以 XML 为基础,并复用同一个XmlHandler。而旧版独立的PomLanguageModule、WsdlLanguageModule、XslLanguageModule均已标记@Deprecated。
各语言对版本与扩展名的支持(来自各 LanguageModule 的LanguageMetadata定义):
| 语言 | 扩展名 | 支持的版本 | 默认版本 |
|---|---|---|---|
| xml | .xml | 1.0 / 1.1 | 1.1 |
| pom | .pom | 4.0.0 | 4.0.0 |
| wsdl | .wsdl | 1.1 / 2.0 | 2.0 |
| xsl | .xsl,.xslt | 1.0 / 2.0 / 3.0 | 3.0 |
解析器的安全与细节配置
XmlParserImpl.java 中对DocumentBuilderFactory的配置值得注意,它兼顾了“保留完整信息”与“防止外部实体攻击”两个目标:
setNamespaceAware(true):解析时感知命名空间,这是后续DomXPathRule命名空间匹配的基础;setIgnoringComments(false)、setIgnoringElementContentWhitespace(false):不丢弃注释和空白,保证 AST 与原文一致;setExpandEntityReferences(true):展开实体引用;- 显式关闭
external-general-entities与external-parameter-entities特性,并挂载一个“永不抛错”的SILENT_ENTITY_RESOLVER,防止未解析的外部资源拖垮分析过程。
解析完成后,DOMLineNumbers.java 会通过字符串定位把行号、列号等坐标信息映射回每个 DOM 节点,这正是 PMD 报告违规位置(以及pmd:startLine/pmd:endLine函数)的基础。
CPD 重复代码检测
四种语言全部支持 CPD。它们共用同一个基于 ANTLR 的词法器 XmlCpdLexer.java(内部委托给由 XMLLexer.g4 生成的XMLLexer)。因此在 CLI 中给任意 XML 文件启用 CPD 即可,无需区分具体方言。
文件归属与 --force-language
语言与文件的关联默认由文件扩展名决定:
- 任何以
.xml结尾的文件都归属xml语言; - 其他 XML 方言使用更具体的扩展名,例如
.xsl、.xslt、.pom、.wsdl。
但不少基于 XML 的格式并不习惯使用.xml扩展名(例如 FXML、Android 布局、Salesforce 元数据等)。要把这些文件交给 XML 语言分析,需要在命令行显式指定--force-language xml,例如:
$ pmd check -d /home/me/src/xml-file.ext -f text -R ruleset.xml --force-language xml在 Windows 上对应写法为:
pmd.bat check -d src\xml-file.ext -f text -R ruleset.xml --force-language xml针对批量的非标准扩展名场景,PMD CLI 参考文档还给出了两种更实用的方式:
- 指定目录:
--force-language xml作用于整个目录时,目录内所有文件都会按 XML 解析;为避免无关文件制造噪音,目录模式下的解析错误会被抑制:
$ pmd check -d src/ -f text -R ruleset.xml --force-language xml- 文件列表(filelist):先用
find等工具筛出指定扩展名的文件写入清单,再配合--file-list精确分析:
$ find src/ -name "*.ext" > filelist.txt $ pmd check --file-list filelist.txt -f text -R ruleset.xml --force-language xmlWindows 下用for /r循环生成 filelist,--file-list参数本身不变。请注意,--force-language只会影响语言检测,规则的匹配仍以规则集内language属性(如xml、xsl)为准。
为 XML 编写 XPath 规则:优先使用 DomXPathRule
在其他语言里,编写 XPath 规则通常使用net.sourceforge.pmd.lang.rule.xpath.XPathRule。但对于 XML 语言,PMD 官方明确不推荐使用该类,而是从 PMD 6.44.0 起建议改用net.sourceforge.pmd.lang.xml.rule.DomXPathRule(源码见 DomXPathRule.java)。
为什么 XPathRule 不适合 XML
XPathRule实际工作在一个“DOM 的包装结构”上,与 XPath 规范并不一致。从DomXPathRule的 Javadoc 和测试 XmlXPathRuleTest.java 可以归纳出两者的主要差异:
- 文本节点表示:
XPathRule用element节点表示文本,与 XPath 规范中 element 与 text 是不同类型节点相悖。例如要表达//elt/text[@Text="abc"]这种写法,在DomXPathRule中应改用标准text()函数,如//elt[text()="abc"]。 - 伪属性:
XPathRule会给每个元素附加@BeginLine、@Text等非 XML 属性,这些属性在DomXPathRule中不可用;对应能力改由 PMD 扩展函数提供,例如用pmd:startLine(.)、pmd:endLine(.)替代@BeginLine/@EndLine的行号比较。 - 根节点:
XPathRule用名为document的 element 充当 AST 根,其节点类型并不正确;DomXPathRule下直接写/RootNode即可,不再需要/document/RootNode前缀。 - 注释与处理指令:
XPathRule会忽略注释和处理指令(例如 FXML 中的<?import javafx.Node ?>);DomXPathRule可以用标准 XPath 语法直接匹配它们。
声明一个 DomXPathRule
与普通 XPath 规则唯一的显著区别是class属性指向DomXPathRule,示例如下:
<rule name="MyXPathRule" language="xml" message="A message" class="net.sourceforge.pmd.lang.xml.rule.DomXPathRule"> <properties> <property name="xpath"> <value><![CDATA[ /a/b/c[@attr = "5"] ]]></value> </property> <!-- 注意:property "version" 不受支持。 --> </properties> </rule>两点使用须知:
- 不支持的属性:
XPathRule的version属性在DomXPathRule中不受支持——它固定按 XPath 2.0 求值,不可配置; - Rule Designer 的局限:PMD 的 Rule Designer 只支持
XPathRule,且其打印的节点树与DomXPathRule使用的 DOM 表示不一致。官方建议改用在线免费的 XPath 测试工具调试你的查询(搜索 “xpath test” 即可找到)。
DomXPathRule 的实现原理
从源码看,DomXPathRule继承AbstractRule,声明了两个字符串属性:
| 属性 | 说明 | 默认值 |
|---|---|---|
xpath | 对根 DOM 求值的 XPath 2.0 表达式 | ""(无默认) |
defaultNsUri | 节点测试所用的默认命名空间 URI,用于匹配声明了 schema 的文档 | "" |
规则初始化时,SaxonDomXPathQuery(见 SaxonDomXPathQuery.java)完成关键准备工作:
- 使用 Saxon 的
XPathEvaluator编译表达式,并预先声明fn命名空间,因此fn:string、fn:starts-with等标准函数开箱即用; - 把
defaultNsUri设置为IndependentContext.setDefaultElementNamespace,实现默认命名空间展开; - 注册 PMD 的全部扩展函数(如
pmd:startLine、pmd:endLine、pmd:fileName),通过XPathHandler与SaxonExtensionFunctionDefinitionAdapter桥接; - 把规则上除
xpath、defaultNsUri、violationSuppressRegex、violationSuppressXPath之外的其他属性自动声明为 XPath 变量,运行时把属性值注入动态上下文——这意味着规则属性可以在表达式中直接引用,实现参数化规则。
每次对节点求值时,规则先把RootXmlNode包装成 Saxon 的DocumentWrapper,表达式命中的节点再通过root.wrap(...)转回 PMD 的XmlNode上报违规。
能做什么:注释、处理指令与标准函数
因为DomXPathRule完全遵循 XPath 规范,你可以:
- 在任意标准 XPath 测试工具中验证查询,也可以借助 StackOverflow 等社区资源学习 XPath 写法;
- 匹配 XML 注释和处理指令;
- 使用
text()、fn:string等标准 XPath 函数。
官方 Javadoc 给出了几个典型例子:
- 找出文件中所有注释:
//comment()- 找出以 "prefix" 开头的一级注释:
/comment()[fn:starts-with(fn:string(.), "prefix")]- 抓取所有
<?import ... ?>处理指令:
/processing-instruction('import')这些用法都有对应的单元测试佐证:测试testComments用/child::comment()[fn:starts-with(fn:string(.), 'suppress')]命中带<!--suppress ... -->的文档;测试testProcessingInstructionsNamed在 FXML 样例上执行/child::processing-instruction('import')并断言命中 5 处 import 指令;测试testTextFunctionInXpath验证//app[text()[1]='app2']可以正常工作——这些都是XPathRule做不到的。
命名空间敏感性(重要)
DomXPathRule是命名空间敏感的。如果被测 XML 文档在根元素上声明了xmlns,你就必须在规则上设置defaultNsUri属性,其值取xmlns对应的 URI;否则节点测试无法匹配,除非使用通配 URI 前缀*:nodeName。
例如对于文档:
<foo xmlns="http://company.com/aschema"> </foo>查询//foo什么都匹配不到,而//*:foo可以。若把规则属性defaultNsUri设为http://company.com/aschema,则//foo会被展开为//Q{http://company.com/aschema}foo从而命中foo节点。带前缀声明的文档行为等价:
<my:foo xmlns:my='http://company.com/aschema'> </my:foo>而对于无命名空间文档:
<foo> </foo>//foo与//*:foo都能匹配,因为//foo被展开为//Q{}foo(本地名 foo、空 URI),恰好与文档的空默认命名空间一致。测试testNamespaceDescendantOkUri、testNamespaceDescendantWildcardUri、testNamespacePrefixDescendantOkUri等完整覆盖了这两类场景(源码见 XmlXPathRuleTest.java)。
另外提醒一点:像Q{...}localName这种显式指定 URI 的写法是XPath 3.1 语法,只在 PMD 7 中可用。
内置规则与规则分类
pmd-xml为四种语言分别准备了规则分类目录,全部位于 pmd-xml/src/main/resources/category/,每个子目录(xml、pom、wsdl、xsl)下都有bestpractices、codestyle、design、documentation、errorprone、multithreading、performance、security等分类文件。
举两个有代表性的内置规则:
- MissingEncoding(xml / bestpractices,见 bestpractices.xml):检查 XML 声明中是否缺失字符编码,XPath 表达式为
/document[@XmlEncoding = ""],命中即报告“Set an explicit XML encoding in the XML declaration to ensure proper parsing”。注意它使用的是XPathRule而非DomXPathRule,恰好印证了两种规则类在 XML 规则体系中共存的事实; - AvoidAxisNavigation(xsl / performance,见 performance.xml):对 XSL 中
following::、preceding::、descendant::等开销巨大的轴导航给出性能警告,并通过checkSelfDescendantAbreviation布尔属性决定是否连//缩写也纳入检查。
这提示了一个实践:如果不需要匹配注释/处理指令、不依赖命名空间语义,存量XPathRule规则仍可继续工作;而新写的、追求规范语义的 XML 规则,应优先选择DomXPathRule。
小结
- PMD 的 XML 支持由
pmd-xml模块承载,以DOM 即 AST为核心设计,四种语言变体(xml、pom、wsdl、xsl)共享同一解析器,PMD 7.13.0 起后三者被重构为 XML 方言; - 非
.xml扩展名的 XML 文件可通过--force-language xml(配合目录或--file-list)纳入分析; - 编写 XML 规则应优先使用
DomXPathRule:它严格遵循 XPath 2.0 规范,支持注释/处理指令匹配、标准函数、命名空间语义与参数化属性,且可直接用任意 XPath 测试工具调试;其局限是 Rule Designer 与version属性不可用。
更深入的规则编写方法(含扩展函数清单)可继续阅读 Writing XPath rules,命令行参数全集见 PMD CLI reference。
- 静态分析
- 代码质量
【免费下载链接】pmd
An extensible multilanguage static code analyzer.
相关推荐
Salt 的 Solaris 11 IPS 包管理模块(solarisipspkg)实战指南:从安装、升级到故障排查
Salt 的 Solaris 11 IPS 包管理模块(solarisipspkg)实战指南:从安装、升级到故障排查 本指南围绕 Salt 执行模块 salt/
静态分析代码质量PMD 对 Velocity 模板语言(VTL)的静态分析支持:从语言模块到规则集实战指南
PMD 对 Velocity 模板语言(VTL)的静态分析支持:从语言模块到规则集实战指南 Velocity 是 Apache 旗下的 Java 模板引擎,允许
静态分析代码质量Semgrep 规则跨语言移植实战指南:语言语法翻译与 AST 驱动的模式适配
Semgrep 规则跨语言移植实战指南:语言语法翻译与 AST 驱动的模式适配 本指南面向需要将既有 Semgrep 规则移植到其他语言的开发者与安全工程师,系
AI 技能AI 插件应用安全网络安全AI 评测
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考