• 静态代码扫描工具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的执行参数)

     1  public 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);
     6         if (ruleSets == null) {
     7             return 0;
     8         }
     9     //对当前参数中的语言进行解析,生成language对象,后续对这个language进行传递,来获得具体的file资源(过滤文件后缀名extension)
    10         Set<Language> languages = getApplicableLanguages(configuration, ruleSets);
    11         List<DataSource> files = getApplicableFiles(configuration, languages);
    12 
    13         long reportStart = System.nanoTime();
    14         try {
    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方法,但最核心的是:

     1 private 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 net.sourceforge.pmd.lang.xml.ast.XmlParser((XmlParserOptions) parserOptions).parse(source);
        }

    这里是new了一个xml工程里ast包下的parser来解析,为什么这样做,是因为开发者考虑了xml的多个parser的可能性,这个ast包外的XmlParser只是个xml工程入口的开端,然后看情况对parser做具体分发.

    实际上ast下的parser的代码是这样的(这里是文件里的xml进行分析生成AST的核心步骤!)

     1  protected Document parseDocument(Reader reader) throws ParseException {
     2         nodeCache.clear();
     3         try {
     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();
    15             return document;
    16         } catch (ParserConfigurationException | SAXException | IOException e) {
    17             throw new ParseException(e);
    18         }
    19     }
    20 
    21 
    22     public XmlNode parse(Reader reader) {
    23     //实际上是用了DOM4J来解析
    24         Document document = parseDocument(reader);
    25         XmlNode root = new RootXmlNode(this, document);
    26         nodeCache.put(document, root);
    27         return 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配置net.sourceforge.pmd.lang.rule.XPathRule,这样最终调用apply就会跑到XpathRule这里,因为这些rule都继承了同样的父类,也还是多态的完美应用.

    1  /**
    2      * xpathRule里的apply方法,最终xpath语句对xml节点的分析落地是在evaluate方法,不展开讲了.细节是使用Jaxen做支持的.
    3      */
    4     @Override
    5     public void apply(List<? extends Node> nodes, RuleContext ctx) {
    6         for (Node node : nodes) {
    7             evaluate(node, ctx);
    8         }
    9     }

    3.后续

    PMD还有很多值得研究的地方,java这块是解析为各种AST节点,然后以访问者模式做visit来实现的,不得不说也很妙...

    后续可能还会对PMD源码继续做分析,看了懂了已经有好多块功能,目前也在做cpp(PMD不支持cpp,只做了cpp的语言的分割,却没做AST生成)这块的规则开发,对PMD又深入了解了许多...

    主要写文章太耗时间了..如果对PMD有什么疑问可以留言问我,尽可能解答.

    转载请注明来源,谢谢
  • 相关阅读:
    关于《注意力模型--Attention注意力机制》的学习
    神经网络参数计算
    FPN(feature pyramid networks)算法讲解
    RetinaNet-focal loss
    论文阅读: RetinaNet
    CNN+LSTM:看图说话
    非极大值抑制-NMS
    python IO文件操作 file文件操作
    软件测试定义 分类
    软件生命周期
  • 原文地址:https://www.cnblogs.com/zhhiyp/p/9147388.html
Copyright © 2020-2023  润新知