跳转到主要内容

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

当您初次登录到 developerWorks 时,将会为您创建一份概要信息。您在 developerWorks 概要信息中选择公开的信息将公开显示给其他人,但您可以随时修改这些信息的显示状态。您的姓名(除非选择隐藏)和昵称将和您在 developerWorks 发布的内容一同显示。

所有提交的信息确保安全。

  • 关闭 [x]

当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

所有提交的信息确保安全。

  • 关闭 [x]

使用 IBM Rational Software Analyzer 进行静态分析,第 4 部分: 集成您自己的分析工具

Steve Gutz, 软件经理, IBM
Steve Gutz 是负责 IBM Rational 代码分析工具的开发经理。除了构架并管理 IBM 的商业产品以外,Steve 过去还致力于 Eclipse Test and Performance Tools project(TPTP),专注于基本的代码审查工具的实现和集成中的改进。在 2002 年加入 IBM 渥太华实验室之前,他在许多公共和私有的公司中担任高级管理和执行职位,包括两个他自己的成功创业。他还写过两本书和许多文章,并且经常在会议上演讲。

简介: 本系列静态分析四篇文章的最后一篇,描述了怎样通过使用 Rational® Software Analyzer 框架提供的 API,来在分析用户界面中,集成一个新的分析引擎。

查看本系列更多内容

发布日期: 2008 年 11 月 07 日
级别: 高级 其他语言版本: 英文
访问情况 : 1417 次浏览
评论: 


IBM® Rational® Software Analyzer 提倡并简化了自动代码质量改进过程。它最初被设计成一个 API 和用户界面,以在其他 Rational 产品中创建并集成静态分析工具,例如,这些 Rational 产品有 Rational® Application Developer 以及 Rational® Software Architect。IBM Rational Software Analyzer 现在已经发展成为一个完整的,能让软件开发员们在他们每天的开发过程中,轻松进行自动代码评审以及结构分析的标准 Provider 。

您将学到的内容

在本系列文章的第 1,2,3 部分,我们已经检查了,用于静态分析的一般用户界面,同样还有执行 Rational Software Analyzer 提供的,Java 代码评审分析 Provider 新规则所需的步骤。到目前为止,关于分析 API 的大多数方面,都最大限度的对您进行了隐藏 ,但是如果您正在阅读本篇文章,那么您必须向工作台,集成一些新形式的分析工具。为了做到这点,您需要再大大深入一些。

在本部分中,我们将进行集成新形式分析工具的过程。其中没有什么太困难的地方,但是,它所涉及到的内容,当然要比添加一个 Java 代码评审规则多得多。当完成本篇文章的学习后,您将从头至尾创建一个新的分析 Provider,并将该 Provider 严密集成到 Rational Software Analyzer 分析框架中去。其中所有的代码以及程序,同样适用于 Rational 建模工具产品的集成:Rational Software Architect,Rational Application Developer,Rational® Software Developer 以及 Rational® Software Modeler。

架构概述

在前面的文章中,我们省略了分析结构的大多数实际细节,这样我们就可以将注意力,专注的放在,用于执行 Java 代码评审规则的某些特定方面上。但是从本篇文章的目的出发,我们将不可避免地考虑,分析框架是怎样实际运行的。同我们在本系列文章中讨论的大多数事情一样,我们尽量减少您阅读理解时的障碍,这样您就可以快速并轻松地掌握文章中涉及到的概念。

接口层次性

理解分析框架的第一步,是查看分析框架中涉及到的元素。图 1 中的图表包含了,创建及运行分析 Provider 所需的多数类以及接口。


图 1. 类和接口图
图表的图


表 1:类以及接口的描述
类或者接口描述
AbstractAnalysisElement所有分析元素的基本类。您可能从来没有直接使用过它,但是它定义了所有分析元素过程的特征。
AbstractAnalysisProvider一个分析 Provider 是个定义一种静态分析工具的元素。另外,Provider 拥有一个或多个目录。例如,Java 代码评审就是一个分析 Provider。
DefaultAnalysisCategory一个类别就是一个对亚类别以及规则进行分类的群体概念,它基于一个公共特征。例如,一个 Threading 类别包含了一系列的与线程相关的规则 。
AbstractAnalysisRule规则就是分析的凭据。正如您在本系列前面文章中看到的那样,规则接受一个或多个分析资源并进行演化。基于演化的结果,可以创建结果 。
AbstractAnalysisResult结果包含的关于一系列资源规则的结果的信息,得以分析。包含特定领域数据的结果,必须由观察者提供。结果并不与规则直接联系,而是由分析历史所拥有(下面将讨论到)。

历史框架

如果您已使用了 Rational Software Analyzer 提供的代码评审特征,那么每次当它运行时,在分析结果视图中都会创建一个新的历史浏览条目。该条目包括了所有 Provider,类别以及在分析启动配置中定义的规则的列表,还有选择用于分析的资源的列表。最后,它还包含了在分析过程中产生的结果的列表 。这一系列的信息集中到一起,就叫做历史,它类似于图 2。


图 2. 历史框架
工作区域的图

如果您更进一步地评审与历史有关的结构(见图 3),您将会注意到类 AbstractAnalysisRule 使用 AnalysisHistory 来储存所有产生的结果。当分析过程开始时,AnalysisHistoryFactory 开始创建历史,该实例还适用于当前过程中涉及到的分析元素的列表 (主要是被选择规则的列表)。当您创建一个新的分析 Provider 时,它怎样运行并不重要,知道分析开始时,您的 Provider 将接受该实例就足够了。


图 3. 分析元素的图表
图表的图

基本过程序列

当用户选择一系列的规则时,他们并没有意识到,他们同时也选择了这些规则拥有的类别以及 Provider。当有人点击 Analyze 按钮,就会创建一个新的历史实例,并且被选择 Provider,类别以及规则的列表,在分析过程开始前就会被联系上。

在分析框架内部,有一个 AnalysisProviderManager 类,它管理着系统中所有的 Provider。当用户开始分析工作区的资源时,该类开始为每一个有功能的 Provider 进行一项异步操作,这称作一个 analyze() 方法。

一般地,每个 Provider 的 analyze() 方法简单地通过它的类别进行重复,并访问它们相应的 analyze() 方法。反过来,每个类别引用它所包含任何规则的 analyze() 方法。然后规则进行一些操作并产生结果,同时产生的,还有评审结果所需的所有特定领域的数据。所有的结果都存储在历史实例中,后者是在分析开始时被创建的 。

您可以看到,这是一个非常简单的过程,但是它已经足够进行任何种类的静态分析。我们已经涉及到了基础工作,这已足够开始创建一个新 Provider 了。

从第一准则创建一个 Provider

在本段中,您将从底层开始创建一个新的分析 Provider 。如果您想为某个特定的领域,创建一个完全崭新的分析回路的话,您可以按照这个精确的过程进行。完成该项的过程几乎是琐碎的,因为您只需要创建一个 Provider 类,一个结果类,以及一个扩展。

注意:
如果您还没有为您的语言领域准备一个已完成的剖析器时,例如 Java 开发工具或者 Eclipse C/C++ Development Tools(CDT),您将需要创建一个 。 Java CC 或者 ANTLR 是完成此项任务的典型工具,但是从本文的目的出发,它们并不是必需的。

创建 Provider 插件

在本例中,您将简单地重复 Rational Software Analyzer 中 C/C++ 分析 Provider 的操作。我们需要的第一个条目是一个插件项目,我们的代码和扩展将放在这个插件项目中。

  1. 选择 File > New > Project 菜单选项,并在随后的条目中,选择 Plug-in Project
  2. 点击 Next,然后在 Names 区域输入 analysis.mycpp
  3. 再次点击 Next, 并输入表格数据(参见图 4):

图 4. 创建一个新插件项目
目录框的图

  1. 为了完成项目的创建,点击 Finish

为了创建分析工具,您需要向插件添加一些附属物。

  1. 在编辑器中打开 META-INF/MANIFEST.MF 文件,并选择 Dependencies 项。
  2. 在 Required Plug-ins 列表中,添加如图 5 所示的插件。

图 5. 添加插件
工作区的图

创建剖析器类

在创建任何种类的 Provider 之前,需要有一些特定领域的剖析器存在。例如,在 Rational Software Analyzer 的 Java 代码评审 Provider 中 ,需要有类 CodeReviewResource 来管理 Java 源文件中的所有查询。在该类中,不需要去创建一个剖析器,因为 Eclipse 已经为 JDT API 访问的 Java,提供了一个完整的 DOM(Document Object Model)。

对于 C 与 C++,我们可以同等的轻松,因为 Eclipse 同样提供了一个叫做 CDT (C/C++ Development Tooling)的开放源项目,它包含了一个目标语言的相似剖析器 。我们在这里并不深入讨论 CRT 的细节,但是您可以在 CDT site 找到所有您需要的,包括 API Javadocs。因为 CDT 的细节部分已经超出了本篇文章的讨论范围,您可以跳过这点。列表 1 所示的代码,是您剖析 C/C++ 代码时所需要使用的 CodeReviewResource 类。它基本上是一个简单的 API 和 CDT,从而简化了它与您的分析 Provider 之间的接口。


列表 1. CodeReviewResource 类
				
package analysis.mycpp;

import java.util.Iterator;
import java.util.List;

import org.eclipse.cdt.core.dom.CDOM;
import org.eclipse.cdt.core.dom.IASTServiceProvider.UnsupportedDialectException;
import org.eclipse.cdt.core.dom.ast.IASTFileLocation;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;

import com.ibm.rsaz.analysis.core.logging.Log;
import com.ibm.rsaz.analysis.core.rule.AbstractAnalysisRule;


public class CodeReviewResource {
private static CDOM dom = CDOM.getInstance();
private IResource resource;
private IASTTranslationUnit resourceCompUnit;


public CodeReviewResource( IResource resource ) { 
this.resource = resource; 
try {
resourceCompUnit = dom.getTranslationUnit( (IFile) resource );
} catch (UnsupportedDialectException e) {
Log.severe( "", e); //$NON-NLS-1$
}
}

public List getTypedNodeList( IASTNode node, int nodeType, boolean searchChildren ) {
CodeReviewVisitor visitor = new CodeReviewVisitor( nodeType, searchChildren );
node.accept( visitor ); 
return visitor.getAstNodeList();
}

public List getTypedNodeList( IASTNode node, int nodeType ) {
return getTypedNodeList( node, nodeType, true );
}

public List getTypedNodeList( IASTNode node, int[] nodeTypes, boolean searchChildren ) {
CodeReviewVisitor visitor = new CodeReviewVisitor( nodeTypes, searchChildren );
node.accept( visitor ); 
return visitor.getAstNodeList();
}

public List getTypedNodeList( IASTNode node, int[] nodeTypes ) {
return getTypedNodeList( node, nodeTypes, true );
}

public void generateResultsForASTNode(AbstractAnalysisRule rule, 
String historyId, IASTNode node ) {
// The location information for this node
IASTFileLocation loc = node.getFileLocation();

// If we get here create a result
CodeReviewResult result = new CodeReviewResult(
resource.getFullPath().toOSString(),
loc.getStartingLineNumber(), loc.getNodeOffset(),
loc.getNodeLength(), node, this );
result.setOwner( rule );

// Save the rule in the history
rule.addHistoryResultSet( historyId, result );
}

public boolean generateResultsForASTNodes(AbstractAnalysisRule rule, 
String historyId, List list ) {
// Crawl through the method invocations
boolean addedResult = false;
for( Iterator it = list.iterator(); it.hasNext(); ) {
generateResultsForASTNode( rule, historyId, (IASTNode)it.next() );
addedResult = true;
}
return addedResult;
}

public IResource getResource() {
return resource;
}

public IASTTranslationUnit getResourceCompUnit() {
return resourceCompUnit;
}
}
      

您可能会注意到参考 CodeReviewResource 是一种叫做 CodeReviewVisitor 的类。这是您需要创建以和 CDT 一起剖析 C/C++ 文件的另一个类。类似于 JDT, CDT DOM 使用一个访问者模板,以和它的剖析树状图交流。因为您有特定的需求,您就需要为您的需求设置一个新的访问器。同样,我们并不讨论该类的具体细节,但是您可以在 CDT Javadoc 中找到关于它的相关文件。列表 2 显示了该应用程序的 visitor 类。


列表 2. Visitor 类
				
package analysis.mycpp;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.cdt.core.dom.ast.ASTVisitor;
import org.eclipse.cdt.core.dom.ast.IASTDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTDeclarator;
import org.eclipse.cdt.core.dom.ast.IASTExpression;
import org.eclipse.cdt.core.dom.ast.IASTInitializer;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTParameterDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTProblem;
import org.eclipse.cdt.core.dom.ast.IASTStatement;
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
import org.eclipse.cdt.core.dom.ast.IASTTypeId;
import org.eclipse.cdt.core.parser.ast.IASTEnumerator;

public class CodeReviewVisitor
extends ASTVisitor
{
public static int TYPE_IASTNode = -1;

public static int TYPE_IASTDeclaration = 12;
public static int TYPE_IASTDeclarator = 14;
public static int TYPE_IASTDeclSpecifier = 15;
public static int TYPE_IASTEnumerator = 21;
public static int TYPE_IASTExpression = 22;
public static int TYPE_IASTGotoStatement = 32;
public static int TYPE_IASTInitializer = 35;
public static int TYPE_IASTName = 40;
public static int TYPE_IASTParameterDeclaration = 43;
public static int TYPE_IASTProblem = 59;
public static int TYPE_IASTStatement = 68;
public static int TYPE_IASTTranslationUnit = 70;
public static int TYPE_IASTTypeId = 71;


private List astNodeList;
private int[] astNodeTypes;
private boolean searchChildren;

public CodeReviewVisitor( int nodeType )
{
astNodeTypes = new int[1];
astNodeTypes[0] = nodeType;
astNodeList = new ArrayList(10);
searchChildren = true;
init();
}

public CodeReviewVisitor( int nodeType, boolean searchChildren )
{
astNodeTypes = new int[1];
astNodeTypes[0] = nodeType;
astNodeList = new ArrayList(10);
this.searchChildren = searchChildren;
init();
}

public CodeReviewVisitor( int[] nodeTypes )
{
astNodeTypes = nodeTypes;
astNodeList = new ArrayList(10);
init();
}

public CodeReviewVisitor( int[] nodeTypes, boolean searchChildren )
{
astNodeTypes = nodeTypes;
astNodeList = new ArrayList(10);
this.searchChildren = searchChildren;
init();
}

private void init() {
shouldVisitNames = true;
shouldVisitDeclarations = true;
shouldVisitInitializers = true;
shouldVisitParameterDeclarations = true;
shouldVisitDeclarators = true;
shouldVisitDeclSpecifiers = true;
shouldVisitExpressions = true;
shouldVisitStatements = true;
shouldVisitTypeIds = true;
shouldVisitEnumerators = true;
shouldVisitTranslationUnit = true;
shouldVisitProblems = true;
}

public List getAstNodeList() {
return astNodeList;
}

private int visitAny( IASTNode node, int nodeTypee) {
for( int iCtr = 0; iCtr < astNodeTypes.length; iCtr++ ) {
if( astNodeTypes[iCtr] == nodeTypee ) {
astNodeList.add(node);
}
}

// Visit children if required
if( searchChildren ) {
return ASTVisitor.PROCESS_CONTINUE;
}
return ASTVisitor.PROCESS_SKIP;
}


public int visit(IASTTranslationUnit node) {
return visitAny( node, TYPE_IASTTranslationUnit);
}

public int visit(IASTName node) {
return visitAny( node, TYPE_IASTName);
}

public int visit(IASTDeclaration node) {
return visitAny( node, TYPE_IASTDeclaration);
}

public int visit(IASTInitializer node) {
return visitAny( node, TYPE_IASTInitializer);
}

public int visit(IASTParameterDeclaration node) {
return visitAny( node, TYPE_IASTParameterDeclaration);
}

public int visit(IASTDeclarator node) {
return visitAny( node, TYPE_IASTDeclarator);
}

public int visit(IASTDeclSpecifier node) {
return visitAny( node, TYPE_IASTDeclSpecifier);
}

public int visit(IASTExpression node) {
return visitAny( node, TYPE_IASTExpression);
}

public int visit(IASTStatement node) {
return visitAny( node, TYPE_IASTStatement);
}

public int visit(IASTTypeId node) {
return visitAny( node, TYPE_IASTTypeId);
}

public int visit(IASTEnumerator node) {
return visitAny( (IASTNode)node, TYPE_IASTEnumerator);
}

public int visit( IASTProblem node ){
return visitAny( node, TYPE_IASTProblem);
} 
}

如果您要使用 CDT,那么您将有一些新的需求:

首先,Eclipse 工作区必须安装 CDT 特征:

  1. 访问 <a href="http://www.eclipse.org/cdt" target="_blank">CDT 网站</a>,并按照上面的安装提示进行安装。

接下来,您需要向插件声明文件添加一个额外的附属物:

  1. 打开声明编辑器,并向所需的插件附属物列表添加 org.eclipse.cdt.core 插件。
  2. 现在您正在同资源协调工作,所以添加 org.eclipse.core.resources 插件以作为一个附属物。

注意到,这里显示的 CodeReviewResource 类是一些黑框。但是,正如您所看到的那样,您正在创建的 Provider 实际上与该类并不直接交流 。实际上,因为这是一个特定的,用于与某个领域剖析器协调工作的 API ,只有规则才会直接与该类相交流。 Provider 唯一的任务,是为分析的每一个资源创建新的剖析器,并将其传递给规则。

创建一个 Result 类

您可以开始创建 C++ 代码评审的最后一步,是定义您预期结果的外观。从 Provider 的角度看,一个分析结果基本上可以被描述成特定领域数据,以及管理这些数据一些基本访问方法的容器。对于一个 C/C++ 结果来说,您需要去定位结果产生的资源的位置,以及涉及代码的起始位置。因为您正在创建的 Provider 使用 CDT,您就可以同样追踪 CDT Abstract Syntax Tree (AST)节点,这样如果需要的话,您就可以找到确切的位置。列表 3 中的代码定义了,您将用于呈现 C/C++ 结果的类。


列表 3. 呈现 C/C++ 结果的类
				
package analysis.mycpp;

import java.io.File;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import com.ibm.rsaz.analysis.core.result.AbstractAnalysisResult;

public class CodeReviewResult
extends AbstractAnalysisResult 
{

private String resourceName = null;
private int lineNumber;
private int startPositionSelection;
private int lengthSelection;
private CodeReviewResource resource = null;
private IASTNode myNode = null;

private static final char LINE_SEP = ':'; 

public CodeReviewResult(String resName, int p_lineNumber, 
int p_startPosSelection, int p_lengthSelection, 
IASTNode node, CodeReviewResource resource){ 
super();

this.resourceName = resName;
this.lineNumber = p_lineNumber;
this.startPositionSelection = p_startPosSelection;
this.lengthSelection = p_lengthSelection;

this.resource = resource;
this.myNode = node;
}


/**
* Overrides the parent class to provide a custom label for
* code review results.
* 
* @return The label string for this result
*/
public String getLabel() { 

String label = super.getLabel();

if( label == null || label.length() == 0 ) {

String shortResName = resourceName.substring( 
resourceName.lastIndexOf( File.separator )+1 );

StringBuffer sb = new StringBuffer();
sb.append( shortName( shortResName, '/' ) )
.append( LINE_SEP )
.append( this.lineNumber )
.append( ' ' )
.append( getLabelWithVariables() ) ;

label = sb.toString();
setLabel( label );

}

return label;
} 

public int getLengthSelection() {
return lengthSelection;
}

public int getLineNumber() {
return lineNumber;
}

public String getResourceName() {
return resourceName;
}

public int getStartPositionSelection() {
return startPositionSelection;
}

public IASTNode getMyNode() {
return myNode;
}

public CodeReviewResource getResource() {
return resource;
}

private String shortName( String sFile, char cSep ) {
int idx = sFile.lastIndexOf( cSep );
return ( idx == -1 ) ? sFile : sFile.substring( idx + 1 );
}
}

这里显示的结果相对来说比较直接。它重载了父类的 getLabel() 方法,以呈现一个通用的结果标签。但是同时,它只包含了基本的特定领域数据。

在类 CodeReviewResult 中,我们有意避免使用 Eclipse 标记,因为在没有文本标记复杂化的基础上,就能充分对其理解。但是,如果您想要创建一个产品级别的 C++ 代码评审 Provider ,那么您就应该使用标记以及注释,以强调某个问题,以及帮助追踪它们的位置。

您将会注意到结果扩展了类 AbstractAnalysisResult,所有的结果都会这样。如果您真想作更多的工作,您可以运行 IAnalysisResult 接口,但是,只是在抽象不能满足您的需要这种少数情况下,您才有必要去这样作。

创建一个基本 Provider 类

因为新的项目和一些前件支持定义的类,所以现在您就可以创建一个分析 Provider 类。

  1. 在插件项目的 analysis.mycpp 包中,创建一个名为 MyCppProvider 的新类。所有的 Provider 必须扩展 AbstractAnalysisProvider 类,该类能够提供一些 Provider 的功能。所有 Provider 类需要的是一个 analyze() 方法。
  2. 编辑您的 Provider 类,代码与列表 4 所示的代码类似。

列表 4. 编辑 Provider 类
				
package analysis.mycpp;

import org.eclipse.core.runtime.IProgressMonitor;

import com.ibm.rsaz.analysis.core.history.AnalysisHistory;
import com.ibm.rsaz.analysis.core.provider.AbstractAnalysisProvider;

public class MyCppProvider extends AbstractAnalysisProvider {
public static final String RESOURCE_PROPERTY = "mycpp.codereview.cpp.resource";
private static final String EXTENSIONS = "c,cpp,h,hpp,hxx";

public void analyze( IProgressMonitor monitor, AnalysisHistory history ) {
}
}

该 Provider 只处理 C 和 C++ 文件,因此,您需要忽略其他种类的文件,因为这些文件可能导致您的剖析器产生错误的结果。幸运的是。静态分析API 在 AnalysisUtil 类中包含了简化的支持。

  1. 添加下面的代码,以作为 Provider analyze() 方法的第一行。
// Obtain the list of valid resource
List<IResource> filteredResources = AnalysisUtil.getFilteredResources(
this.getResources(), EXTENSIONS, CProjectNature.C_NATURE_ID );
// Save the list of resources that were analyzed
history.setAnalyzedResources( filteredResources );

这些代码行从传递给 Provider 的整个列表中,构建了文件源的一个分列表,它只包含了 C 和 C++ 语言使用的扩展文件。为了对所有这些文件进行代码复查,您需要通过该列表进行重复。

  1. 向方法 analyze() 的末尾添加如下的代码:
for( Iterator it = filteredResources.iterator(); it.hasNext(); ) 
{ 
    CodeReviewResource codeReviewRes=new CodeReviewResource((IResource)it.next() ); 
    setProperty( history.getHistoryId(), RESOURCE_PROPERTY, codeReviewRes ); 
    }

该代码通过每个资源进行遍历,并创建了您在前面创建的类 CodeReviewResource (C++ 剖析器)的一个实例。该代码同时还设置了包含该实例的 Provider 的属性。

提示:
扩展 AbstractAnalysisProvider 的分析 Provider 拥有一个属性列表,该列表可被用于共享有关子类别与规则的信息。另外如果有需要,它还可被用于共享规则之间的信息。C/C++ 分析 使用该设置,来向每个规则传递当前的代码评审资源。注意每一个分析历史包含了它自己的属性列表。因此,任意一个 Provider 可能都有一些属性设置,当用户在分析结果视图中删去一条历史记录时,这些属性设置将会被自动删去。这就是该设置的 API。

for( Iterator it = filteredResources.iterator(); it.hasNext(); ) 
{
 CodeReviewResource codeReviewRes=new CodeReviewResource((IResource)it.next() );
 setProperty( history.getHistoryId(), RESOURCE_PROPERTY, codeReviewRes );
}

Provider 还需要注意过程监视器,因为用户需要反馈,并可能选择取消分析过程。

  1. 编辑方法 analyze() 让它和列表 5 相似。

列表 5. 编辑 analyze() 方法
				
public void analyze( IProgressMonitor monitor, AnalysisHistory history ) {
// Obtain the list of valid resource
List<IResource> filteredResources = AnalysisUtil.getFilteredResources( 
this.getResources(), EXTENSIONS, CProjectNature.C_NATURE_ID );


// Save the list of resources that were analyzed
history.setAnalyzedResources( filteredResources );
monitor.beginTask( getLabel(), filteredResources.size() );

// Iterate through each selected resource file
for( IResource resource : filteredResources ) {
if( monitor.isCanceled() ) {
tearDown();
return;
}

CodeReviewResource codeReviewRes = new CodeReviewResource( resource, true); 
setProperty( history.getHistoryId(), RESOURCE_PROPERTY, codeReviewRes );

// Do work here

monitor.worked( 1 );
}

removeProperty(history.getHistoryId(), RESOURCE_PROPERTY); 
monitor.done();
tearDown();
}

一个 Provider 的 analyze() 方法的基本任务,是向它拥有的所有类别发送一条“analyze”信息。但是,因为用户可能并没选择所有的类别,因此 Provider 需要去确保,只向那些用户选中的类别发送该条信息。解决该问题的方案是,通过 Provider 含有的元素(通常只限于类别元素)进行迭代,并访问它们的 analyze() 方法。

  1. 在 Provider analyze() 方法中,用以下的代码取代“Do work here”注释:
for( AbstractAnalysisElement element : getOwnedElements() ) {
if( monitor.isCanceled() ) {
tearDown();
return;
}

// Get the unbquitous category
DefaultAnalysisCategory category = (DefaultAnalysisCategory)element;
if( history.containsAnalysisElement( category ) ) {
category.analyze( history );
}
}

您需要再次检查过程监视器,以确保用户并没有取消分析操作。为了测试一个类别是否可用,您可以利用历史实例的特征。方法 AnalysisHistory.containsAnalysisElement() 接受任何分析元素,并在静态分析过程开始前,测试它是否被用户选中。如果含有的类别出现在历史中,那么 Provider 就可以访问它的 analyze() 方法。

C/C++ 代码评审的完整分析 Provider 类,应该与列表 6 中的代码类似。唯一增加的是访问类的末尾,以除掉您设置的属性,这只是一项预防措施,确保存储的不会意外的逐渐消失。


列表 6. C/C++ 代码评审的完整分析提供类
				
package com.ibm.rsaz.analysis.codereview.cpp;

import java.util.List;

import org.eclipse.cdt.core.CProjectNature;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IProgressMonitor;

import com.ibm.rsaz.analysis.core.AnalysisUtil;
import com.ibm.rsaz.analysis.core.category.DefaultAnalysisCategory;
import com.ibm.rsaz.analysis.core.element.AbstractAnalysisElement;
import com.ibm.rsaz.analysis.core.element.AnalysisParameter;
import com.ibm.rsaz.analysis.core.history.AnalysisHistory;
import com.ibm.rsaz.analysis.core.provider.AbstractAnalysisProvider;
import com.ibm.rsaz.analysis.core.result.AbstractAnalysisResult;
import com.ibm.rsaz.analysis.core.rule.AbstractAnalysisRule;

public class CodeReviewProvider
extends AbstractAnalysisProvider
{
private static final String EXTENSIONS = "c,cpp,h,hpp,hxx";
public static final String RESOURCE_PROPERTY = "codereview.cpp.resource";



/**
* Analyze the categories selected by the user
* 
* @param parentMonitor The progress monitor of the parent task
* @param history A reference to the history record for this analysis
* 
*/
public void analyze( IProgressMonitor monitor, AnalysisHistory history ) {
// Obtain the list of valid resource
List<IResource> filteredResources = AnalysisUtil.getFilteredResources(
this.getResources(), EXTENSIONS, CProjectNature.C_NATURE_ID );


// Save the list of resources that were analyzed
history.setAnalyzedResources( filteredResources );

monitor.beginTask( getLabel(), filteredResources.size() );


// Iterate through each selected resource file
for( IResource resource : filteredResources ) {
if( monitor.isCanceled() ) {
tearDown();
return;
}

CodeReviewResource codeReviewRes = new CodeReviewResource( 
resource, true );
setProperty( history.getHistoryId(), RESOURCE_PROPERTY, codeReviewRes );

for( AbstractAnalysisElement element : getOwnedElements() ) {
if( monitor.isCanceled() ) {
tearDown();
return;
}

// Get the unbquitous category
DefaultAnalysisCategory category = (DefaultAnalysisCategory)element;
if( history.containsAnalysisElement( category ) ) {
category.analyze( history );
}
}

monitor.worked( 1 );
}

removeProperty(history.getHistoryId(), RESOURCE_PROPERTY); 
monitor.done();
tearDown();
}
}

创建一个 Provider 插件扩展

随着现在 Provider 类的完成,您又面临一个新的问题,因为现在您需要将此项新功能插入 Eclipse 中。解决这个问题的方法很简单:您可以定义一个扩展点,从而允许分析工具找到 C++ 分析功能。

  1. 打开插件声明文件,并选择 Extensions 项。
  2. 点击 Add 按钮,并在 New Extension 目录下,选择 com.ibm.rsaz.analysis.core.analysisProvider 扩展点。该扩展点用于识别新的分析 Provider。
  3. 分析 Provider 扩展现在应该列于 All Extensions 列表中。右击该条目,并选择 New > analysisProvider,如图 6 所示。

图 6. 定义扩展点
菜单的图

选择添加的 analysisProvider 条目,完成该表,使其如图 7 所示:

  • 注意 Class 区域包含了在前面章节中创建的 Provider 类的名字。
  • ID 区域应该是一个足够独一无二的标识符(类名可以很适合它,但是它可以是任何独一无二的字符串。
  • Label 区域包含了,当分析配置目录可见时,对用户可见的字符串。理想条件下,标签会是一个从 plugin.properties 文件中获得的固定字符串,但是在目前的情况下,使用一个硬编码是为了简化性。

我们将在稍后讨论剩余的区域。现在,把它们留成空白,如图 7 所示。


图 7. 完成扩展元素细节
工作区的图

信不信由您,您所要做的只是向静态分析框架,插入一个 Provider。如果您为您的 Eclipse 开发环境运行一个实时工作台的话,您可以证明,您的新 Provider 现在就可以使用了(参见图 8)。


图 8. 验证分析 Provider
工作区的图

当然在这个地方, Provider 是不可用的,因为它没有定义任何种类或者规则。但是它定义了一个真实的分析规则引擎。所有现在,让我们给它一个目的。

添加一个分析类别

完成一个实用分析 Provider 的第一步,是添加一个或多个类别。所有的类别必须扩展 DefaultAnalysisCategory 类,对于您将要创建的任何类别,您都可以直接使用该类。但是,您也可以扩展该类并超越它的方法,这样如有需要,它就可以提供额外的功能。因为 C++ 的类别没有任何特别的地方,所以您就可以直接使用默认的类。但是如果您仔细检查该类的话,您就会发现,同一个分析 Provider 类一样,它有一个定义的 analyze() 方法,如列表 7 所示。


列表 7. DefaultAnalysisCategory 类
				
public void analyze( AnalysisHistory history ) {
// Analyze any nested categories
List categories = getOwnedElements();
if( categories != null ) {
for( Iterator it = categories.iterator(); it.hasNext(); ) {
AbstractAnalysisElement element = (AbstractAnalysisElement)it.next();
if( history.containsAnalysisElement( element ) ) {
element.addHistoryResultSet( history.getHistoryId() );

if( element.getElementType() == CATEGORY_ELEMENT_TYPE ) {
DefaultAnalysisCategory category = (DefaultAnalysisCategory) element;
try {
category.analyze(history);
} catch (Exception e) {
Log.severe(CoreMessages.bind(
CoreMessages.execute_analyzeCategory_failure, category.getLabel()),e);
}
} else if( element.getElementType() == RULE_ELEMENT_TYPE ) {
AbstractAnalysisRule rule = (AbstractAnalysisRule)element;
try {
AnalysisHistoryElement historyElement = history.getHistoryElement(rule);
historyElement.startElapsedTimer();
rule.analyze(history);
historyElement.stopElapsedTime();
} catch (Exception e) {
Log.severe(CoreMessages.bind(
CoreMessages.execute_analyzeRule_failure, rule.getLabel()),e);
}

}
}
}
}
}

该段代码中重要的一点是,显示了一个类别可以拥有规则或者其他类别(它们可以被联系在一起)。由于这个原因,在任何含有元素的 analyze() 方法可以被访问之前,类别需要知道,该元素是一个规则,还是一个类别。它使用 AbstractAnalysisElement.getElementType() 方法来解决这个问题。

这里另一个需要注意的地方,是在执行一项规则之前,代码开始后,在规则完成的时候终止代码。这用于追踪每项规则的总的执行时间。

如果您想要创建您自己的通用类别,并执行一个新 analyze() 方法的话,您需要意识到以上注意点,并确保您自己的类别以同样的方式运行 。否则,您将会遇到无法预料的状况,避免这种情况的最简单的方式是,复制默认的类别 analyze() 方法,并修改它使其满足您的需要。

在您拥有一个类别类以后(在目前的情况下是 DefaultAnalysisCategory),您需要将它与您的 Provider 联系起来。

  1. 为了完成这点,您需要再一次返回到插件声明,并添加一个新的扩展。这次是:com.ibm.rsaz.analysis.core.analysisCategory
  2. 右击该扩展,添加一个新的 analysisCategory,并得到如图 9 所示的结果表。

图 9. 将类别类与分析 Provider 联系起来
工作区的图

注意 Provider 区域的内容是,早期创建的 Provider 的唯一标识符。这意味着,最高层的新类别直接为 Provider 所有。当创建一个亚类别时,Provider 区域应该被留成空白。与之不同的是,表格中的 Category 区域可以输入父类别的 ID。

添加一条规则

规则创建在本系列文章的第 2 部分已详细解释过( C++ Provider 规则结构与讨论的 Java 有细微的差别),所以我们在这里不再赘述。您可能会想,添加一条新规则,要比创建规则类和向插件添加一个扩展容易。

本例中的规则类,如同在同一行中定义多个变量,例如:

int i,j,k;

  1. 在插件的 src 文件夹中,创建一个名为 RuleMutipleDeclarations 的新类。
  2. 向该类添加如列表 8 所示的代码。

列表 8. 添加一条规则


列表 8. 向 RuleMutipleDeclarations 类添加的代码
				
package analysis.mycpp;

import java.util.Iterator;
import java.util.List;

import org.eclipse.cdt.core.dom.ast.IASTDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclaration;

import com.ibm.rsaz.analysis.core.history.AnalysisHistory;
import com.ibm.rsaz.analysis.core.rule.AbstractAnalysisRule;

public class RuleMutipleDeclarations
extends AbstractAnalysisRule
{
/**
* Analyze this rule
* 
* @param history A reference to the history record for this analysis
*/
public void analyze( AnalysisHistory history) {
CodeReviewResource resource = (CodeReviewResource)getProvider().getProperty( 
history.getHistoryId(), MyCppProvider.RESOURCE_PROPERTY );

List list = resource.getTypedNodeList( resource.getResourceCompUnit(),
CodeReviewVisitor.TYPE_IASTDeclaration );
for( Iterator it = list.iterator(); it.hasNext(); ) {

// We only want to work with simple declarations
IASTDeclaration decl = (IASTDeclaration)it.next();
if( decl instanceof IASTSimpleDeclaration ) {
IASTSimpleDeclaration simpleDecl = (IASTSimpleDeclaration)decl;

// If there is more than one declaration, create a match
if( simpleDecl.getDeclarators().length > 1 ) {
resource.generateResultsForASTNode( this,
history.getHistoryId(), decl );
}
}
}
}
}

我们不会深入讨论它的具体工作细节,但是基本来说,它首先参考类 CodeReviewResource ,您可以回忆一下,该类就是被分析的 C++ 类 Provider 创建的剖析器。然后该类使用一些剖析器 API ,以得到声明的一个列表。每个声明将被测试,以确保声明的数目只限于 1 (一个) 。如果不是,通过使用来自您早期创建的 CodeReviewResource 类的附加 API,来产生一个结果。

  1. 过程的最后一步,是通过使用 com.ibm.rsaz.analysis.core.analysisRule 扩展点来添加规则扩展 。确保您的扩展,与图 10 中显示的扩展相似。(注意 category 区域的内容,是在前面段落中创建的类别的唯一标识符)。

在本系列文章的第 2 部分讨论过的其他方面,在这里也得到应用,例如,您可以寻求自动代码设置,用户帮助以及等等之类的支持。


图 10. 添加规则扩展
工作区的图

结果检查

到目前为止,您还不能显示在分析结果视图中得到的结果。这是分析浏览器需要做的, Rational Software Analyzer 框架提供接口 IAnalysisViewer,以及管理该接口的名为 AbstractAnalysisViewer 的一个抽象类。回忆由 C++ 规则创建的结果,包含了大量的有用信息,包括 CDT 节点信息,行号,以及更多。当您想要检查一个结果时,您可以利用这些信息,在文本编辑器中定位 C++ 源文件,并强调出问题文本。

在您可以运行分析检查器之前,您需要对其有一定了解。检查器出于呈递规则结果数据考虑,可以与任何分析元素联系起来。例如,检查器不需要与一个规则相联系, Provider 与类别也可以参考检查器。在这种情况下,检查器可适用于, Provider 或者类别的所有权链内的任何规则。考虑如下规则结构:

Provider A
Category B
Rule C
Rule D
Category E
Rule F

假设检查器与 Provider A 向联系,当 getViewer() 方法访问规则 C,D 或者 F 时,它们将会首先检查它们自己的检查器参考。如果没有发现任何检查器,那么规则就会参考它们拥有的类别。在这种情况下,类别并没有分配的检查器,因此,它们将参考它们拥有的 Provider ,该 Provider 返回您分配的检查器。现在进一步假设,向 Rule C 分配了一个不同的检查器。那么在这种情况下,除了 C 以外的所有规则,将使用 Provider 的同一默认检查器,而 C 将会使用分配给它的检查器。

退回方案允许单个检查器,去处理所有呈递的规则结果,并允许不同的检查器联系树状图的任何地方 ,以替换默认的行为。在理解这个之后,让我们去浏览一个检查器实例 。


列表 9. 检查器代码实例
				
package analysis.mycpp;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.texteditor.ITextEditor;

import com.ibm.rsaz.analysis.core.AnalysisConstants;
import com.ibm.rsaz.analysis.core.logging.Log;
import com.ibm.rsaz.analysis.core.result.AbstractAnalysisResult;
import com.ibm.rsaz.analysis.core.viewer.IAnalysisViewer;

public class CodeReviewViewer implements IAnalysisViewer {
public void showView( final AbstractAnalysisResult result )
{

final CodeReviewResult specificResult = (CodeReviewResult)result;

Job job = new Job( "Open C++ File" ) {
protected IStatus run( IProgressMonitor monitor )
{
Display.getDefault().asyncExec(new Runnable() {
public void run() {
Path path = new Path(specificResult.getResourceName() ); 
IFile file = ResourcesPlugin.getWorkspace()
.getRoot().getFile(path);

if ( ( file != null ) && file.exists() ) {
try {
ITextEditor editor = (ITextEditor)IDE.openEditor(
PlatformUI.getWorkbench()
.getActiveWorkbenchWindow()
.getActivePage(), file, true ); 

// If there are unsaved changes in the editor,
// save them before trying to highlight a result
if( editor.isDirty() ) {
editor.doSave( null );
}

editor.selectAndReveal( specificResult
.getStartPositionSelection(),
specificResult.getLengthSelection() );

} catch ( PartInitException e ) {
Log.severe( AnalysisConstants.BLANK, e );
}
}
}
});
return Status.OK_STATUS;
}
};
job.schedule();
}
}

在本例中有几个需要注意的地方:

  • 检查器类执行 IAnalysisViewer 接口,并使该接口调用 showView() 方法。 简单来说,检查器代码从结果中获取资源,并为该资源创建一条路径。该路径被传递给文本编辑器用于显示,最终,编辑器被定位,这样 CDT 节点在编辑器中就可见并被选中。
  • 为了改进用户的感受,所有的工作都应该在 Eclipse 中完成。

为了让该检查器类具有功能,您需要向插件文件添加另外一个扩展:

  1. 打开 plugin.xml 文件,并选择 Extensions 项。
  2. com.ibm.rsaz.analysis.core.analysisViewer 添加一个新的扩展,并添加该表格的内容,如图 11 所示。

图 11. 激活检查器类
工作区的图

  1. 接下来,通过在 Provider 提供的区域,输入检查器的唯一标识符,来将检查器与 Provider 联系起来(参见图 12)。

图 12. 将检查器与 Provider 联系起来
工作区的图

最后一步,您需要纠正检查器类中的编辑错误。这意味着,在 Eclipse IDE 中需要应用附加的插件附件,以支持编辑。

  1. 选择 Dependencies 项,并向所需插件的列表添加 org.eclipse.ui.workbench.texteditor org.eclipse.ui.ide
  2. 为了测试检查器,再次运行 C++ 分析工具(对产生结果的源代码)。
  3. 在分析结果视图中双击一个结果。Eclipse 工作台将打开一个编辑器,该编辑器包含并显示出了与正确类对比的错误。

外部静态分析引擎

分析 Provider 被忽略的一个方面,是它可以集成已存在分析引擎的能力。既然该 Provider 与特定领域的引擎密切相关,我们在这里并不讨论它。但是,假设有 API 访问了引擎,那么向 Rational Software Analyzer 框架插入该 API 就成为可能。在这种情况下, Provider ,类别以及规则可以简单集中了,外部引擎具有的功能,例如:

  • 如果外部引擎已经支持类别,优先考虑 Provider 中的 $getOwnedElements() 方法,以返回在类 DefaultAnalysisCategory 中的类别列表。
  • 如果规则存在,优先考虑类别类中的相同 getOwnedElements() 方法,以返回一列 AbstractAnalysisRule 实例,该实例反映了真实的分析引擎支持什么。

总结以及后续

在本篇文章中,我们已经学习了 APIs,以及从底层构建新分析 Provider 需要的大多数方面 ,包括类别以及规则的定义。同时我们还简略的学习了,怎样去定义一个分析结果类,并给出了剖析器的概述。基本上,不管用于分析的语言是哪种,构建 Provider 的步骤与例子中的大体相似。

在本系列的后续文章中,我们将寻求访问 Rational Software Analyzer 功能的方法,以帮助向任何的构建系统集成该工具。


参考资料

学习

获得产品和技术

讨论

关于作者

Steve Gutz 是负责 IBM Rational 代码分析工具的开发经理。除了构架并管理 IBM 的商业产品以外,Steve 过去还致力于 Eclipse Test and Performance Tools project(TPTP),专注于基本的代码审查工具的实现和集成中的改进。在 2002 年加入 IBM 渥太华实验室之前,他在许多公共和私有的公司中担任高级管理和执行职位,包括两个他自己的成功创业。他还写过两本书和许多文章,并且经常在会议上演讲。

关于报告滥用的帮助

报告滥用

谢谢! 此内容已经标识给管理员注意。


关于报告滥用的帮助

报告滥用

报告滥用提交失败。 请稍后重试。


developerWorks:登录


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 使用条款

 


当您初次登录到 developerWorks 时,将会为您创建一份概要信息。您在 developerWorks 概要信息中选择公开的信息将公开显示给其他人,但您可以随时修改这些信息的显示状态。您的姓名(除非选择隐藏)和昵称将和您在 developerWorks 发布的内容一同显示。

请选择您的昵称:

当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

(长度在 3 至 31 个字符之间)


单击提交则表示您同意developerWorks 的条款和条件。 使用条款.

 


为本文评分

评论

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Rational
ArticleID=350411
ArticleTitle=使用 IBM Rational Software Analyzer 进行静态分析,第 4 部分: 集成您自己的分析工具
publish-date=11072008
author1-email=sgutz@ca.ibm.com
author1-email-cc=

标签

Help
使用 搜索 文本框在 My developerWorks 中查找包含该标签的所有内容。

使用 滑动条 调节标签的数量。

热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。

我的标签 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。

使用搜索文本框在 My developerWorks 中查找包含该标签的所有内容。热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。我的标签 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。