静态代码扫描工具PMD分析XML的核心源码解读(从core主入口到子语言解析)

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

静态代码扫描⼯具PMD分析XML的核⼼源码解读(从core主
⼊⼝到⼦语⾔解析)
本⽂基于6.1版本的PMD.如果你是⼯作中⽤到这个⼯具,请结合你的源码看.因为源码分析实际上DEBUG也能知道,但PMD涉及的类⾮常多,⼗遍debug 也不能掌握⼤部分吧.
0. ⼤纲
a) Core篇
b) XML篇
c) 后续
1.Core篇
PMD⽀持对XML⽂件的扫描,本⾝对xml的规则⽀持⾮常少,⽽且还分为xml,POM,wsdl等部分.看起来有四个ruleSets(规则集)实际上,没有⼏个规则(笑).本⽂主要对xml的解析进⾏源码跟踪解读,core篇涉及部分为参数配置的封装,对应parser(解析器)的⽣成,xpathRule的讲解.可能有部分漏,请谅解,本⽂⼤致做类的说明和⽅法调⽤,不写最详细的callgraph,因为我认为⼏个功能⼊⼝找对,便把握了核⼼.
⾸先PMD的解析⼊⼝是Core包下的net.sourceforge.pmd.PMD,⼊⼝⽅法是main,核⼼分析⽅法是doPMD⽅法.(下次会简单讲解下PMD的执⾏参数)
1public static int doPMD(PMDConfiguration configuration) {
2
3//使⽤⼯⼚⽅法来加载ruleSet 规则集.会将配置configuration对象⾥的ruleSet属性转换成对应的ruleSet..ruleSet是rule规则类的集合
4 RuleSetFactory ruleSetFactory = RulesetsFactoryUtils.getRulesetFactory(configuration, new ResourceLoader());
5 RuleSets ruleSets = RulesetsFactoryUtils.getRuleSetsWithBenchmark(configuration.getRuleSets(), ruleSetFactory);
6if (ruleSets == null) {
7return 0;
8 }
9 //对当前参数中的语⾔进⾏解析,⽣成language对象,后续对这个language进⾏传递,来获得具体的file资源(过滤⽂件后缀名extension)
10 Set<Language> languages = getApplicableLanguages(configuration, ruleSets);
11 List<DataSource> files = getApplicableFiles(configuration, languages);
12
13long reportStart = System.nanoTime();
14try {
15 Renderer renderer = configuration.createRenderer(); //根据参数⽣成对应的render,输出类(以固定格式输出到⽂件)
16 List<Renderer> renderers = Collections.singletonList(renderer);
17
18 renderer.setWriter(IOUtil.createWriter(configuration.getReportFile()));
19 renderer.start();
20 (22)
23 RuleContext ctx = new RuleContext();
24 ...36 //对⽂件流进⾏具体分析的⼊⼝,指定了规则集⼯⼚,⽂件,ctx(上下⽂,在规则调⽤中传递当前分析⽂件路径等信息,render是最终渲染输出的类) 37 processFiles(configuration, ruleSetFactory, files, ctx, renderers);
38 ...
从processFiles进⼊,会启⽤线程,并传⼊⽂件类,规则上下⽂
if (configuration.getThreads() > 0) { //多线程类
new MultiThreadProcessor(configuration).processFiles(silentFactoy, files, ctx, renderers);
} else { //单线程类
new MonoThreadProcessor(configuration).processFiles(silentFactoy, files, ctx, renderers);
}
AbstractPMDProcessor是MultiThreadProcessor的⽗类,下次有空会简单分析下PMD的多线程使⽤,这也是让我学到⼀些并发库知识的地⽅.
processFiles⾥核⼼代码是这⼀句:
runAnalysis(new PmdRunnable(dataSource, niceFileName, renderers, ctx, rs, processor));
简单来讲,PMD将⼀系列需要的参数封装到PmdRunnable⾥⾯,这个Runnable实际上是Callable的实现类,是通过这个类的call⽅法⾥调⽤到:
sourceCodeProcessor.processSourceCode(stream, tc.ruleSets, tc.ruleContext);
SourceCodeProcessor顾名思义就是核⼼的代码源分析类.调⽤processSourceCode后.对参数做各种封装,再调⽤⾃⾝的好⼏个
processSource⽅法,但最核⼼的是:
1private void processSource(Reader sourceCode, RuleSets ruleSets, RuleContext ctx) {
2 LanguageVersion languageVersion = ctx.getLanguageVersion();
3 LanguageVersionHandler languageVersionHandler = languageVersion.getLanguageVersionHandler();
4//⽣成对应语⾔的解析器,我们这⾥⽣成XmlParser
5 Parser parser = PMD.parserFor(languageVersion, configuration);
6//调⽤XmlParser的parse⽅法解析资源代码
7 Node rootNode = parse(ctx, sourceCode, parser);
8 symbolFacade(rootNode, languageVersionHandler);
9 Language language = languageVersion.getLanguage();
10 ...
11 List<Node> acus = Collections.singletonList(rootNode);
12//规则分析代码的核⼼!!
13 ruleSets.apply(acus, ctx, language);
14 }
这⾥算是离最终的解析Node最近的⼀步了.PMD使⽤了⼤量的parser但是有很好的抽取和抽象⽗类,⼀切基于java的多态得以完美运作.
⾄此,core篇的⼏个重要的类就这样.(我省略了好多⽬前⽆关紧要的类)
2.XML篇
core篇最终是⽣成对应的parser,⽽这⾥就是XmlParser了,来看看parse⽅法内部是什么.
public Node parse(String fileName, Reader source) throws ParseException {
return new ng.xml.ast.XmlParser((XmlParserOptions) parserOptions).parse(source);
}
这⾥是new了⼀个xml⼯程⾥ast包下的parser来解析,为什么这样做,是因为开发者考虑了xml的多个parser的可能性,这个ast包外的XmlParser只是个xml⼯程⼊⼝的开端,然后看情况对parser做具体分发.
实际上ast下的parser的代码是这样的(这⾥是⽂件⾥的xml进⾏分析⽣成AST的核⼼步骤!)
1protected Document parseDocument(Reader reader) throws ParseException {
2 nodeCache.clear();
3try {
4 String xmlData = IOUtils.toString(reader);
5
6 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
7 ... //做⼀些set,防XXE攻击
8 DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
9 documentBuilder.setEntityResolver(parserOptions.getEntityResolver());
10 Document document = documentBuilder.parse(new InputSource(new StringReader(xmlData)));
11 //解析⽣成⾏号的核⼼步骤
12 DOMLineNumbers lineNumbers = new DOMLineNumbers(document, xmlData);
13 //⽣成⾏号的⽅法.determine 可以点源码进去看看
14 lineNumbers.determine();
15return document;
16 } catch (ParserConfigurationException | SAXException | IOException e) {
17throw new ParseException(e);
18 }
19 }
20
21
22public XmlNode parse(Reader reader) {
23 //实际上是⽤了DOM4J来解析
24 Document document = parseDocument(reader);
25 XmlNode root = new RootXmlNode(this, document);
26 nodeCache.put(document, root);
27return root;
28 }
简单理解:PMD的XML的AST的⽣成是基于DOM4J做xml⽂件分析成DOM树,然后做⾏列号解析和封装的.(⽐java代码的解析要简单⼤概100倍吧,java是基于javacc的),这是xml的node基本类:
public interface XmlNode extends Node, AttributeNode {
String BEGIN_LINE = "pmd:beginLine";
String BEGIN_COLUMN = "pmd:beginColumn";
String END_LINE = "pmd:endLine";
String END_COLUMN = "pmd:endColumn";
//w3c的node + 各种⾏列号 = PMD的XmlNode
org.w3c.dom.Node getNode();
}
⾄此,xml的Node(AST)已经⽣成,重新返回到CORE⼯程的rule下.
为了加快进度,我就简单讲解下.
在ruleSets.apply(List<Node>, ctx, language)⾥,ruleSets是rule类的集合,接下来PMD做的事情⾮常简单,就是遍历ruleSet⾥的rule,并让rule来apply每⼀个⽂件.
如果在规则集的配置⾥使⽤的是xpath,那就必须在class配置ng.rule.XPathRule,这样最终调⽤apply就会跑到XpathRule这⾥,因为这些rule都继承了同样的⽗类,也还是多态的完美应⽤.
1/**
2 * xpathRule⾥的apply⽅法,最终xpath语句对xml节点的分析落地是在evaluate⽅法,不展开讲了.细节是使⽤Jaxen做⽀持的.
3*/
4 @Override
5public void apply(List<? extends Node> nodes, RuleContext ctx) {
6for (Node node : nodes) {
7 evaluate(node, ctx);
8 }
9 }
3.后续
PMD还有很多值得研究的地⽅,java这块是解析为各种AST节点,然后以访问者模式做visit来实现的,不得不说也很妙...
后续可能还会对PMD源码继续做分析,看了懂了已经有好多块功能,⽬前也在做cpp(PMD不⽀持cpp,只做了cpp的语⾔的分割,却没做AST⽣成)这块的规则开发,对PMD⼜深⼊了解了许多...
主要写⽂章太耗时间了..如果对PMD有什么疑问可以留⾔问我,尽可能解答.。

相关文档
最新文档