Static analysis IBM Rational Software Analyzer: Part 4. Integrating your own analysis tools

This final article in the four-part static analysis series describes how to integrate a new analysis engine in the analysis user interface by using the API supplied in the Rational® Software Analyzer framework.

Share:

Steve Gutz (sgutz@ca.ibm.com), Software Manager, IBM

Steve GutzSteve Gutz is a development manager responsible for IBM Rational’s code analysis tools. In addition architecting and managing IBM’s commercial products Steve is also a past contributor on the Eclipse Test and Performance Tools project (TPTP), focusing on improvements in implementation and integration of basic code review tools. Previous to joining the IBM Ottawa Lab in 2002, he held senior management and executive positions in several public and private companies including two of his own successful start-ups. He is also the author of two books and many articles, and is a regular conference speaker.



29 April 2008

Also available in Chinese

IBM® Rational® Software Analyzer encourages and simplifies the process of automated code quality improvements. It was initially designed as an API and user interface to create and integrate static analysis tools into other Rational products, such as Rational® Application Developer and Rational® Software Architect. It has evolved into a complete, standalone offering that enables software developers to easily use automated code review and structural analysis in their daily development process.

What you will learn

In Parts 1, 2, and 3 of this series of articles, we examined the common user interface for static analysis, as well as the steps required to implement new rules for the Java code review analysis provider supplied with Rational Software Analyzer. So far, most aspects of the analysis API have been largely hidden from you, but if you are reading this article, then you must have a need to integrate some new form of analysis into the workbench. To accomplish this, you need to dive a lot deeper.

In this part, we will ease into the process of integrating new forms of analysis. There won't be anything too difficult here, but it is certainly more involved than adding a Java code review rule. By the time you are finished with this article, you will have a new analysis provider built from the ground up and seamlessly integrated into the Rational Software Analyzer analysis framework. All of the code and procedures described also apply to integrations with the Rational modeling products: Rational Software Architect, Rational Application Developer, Rational® Software Developer, and Rational® Software Modeler.

Architectural overview

In the previous articles, we have skirted most of the real details of the analysis architecture so that we could focus, instead, on specific pieces of the API used to implement Java code review rules. Given the objectives for this article, we can no longer avoid talking about how the analysis framework really operates. Like most things that we have talked about in this series, we make every effort to reduce the barrier to entry, and you should be able to grasp the concepts fairly quickly.

Interface hierarchy

The first step toward understanding the analysis framework is to look at the elements that are involved in analysis. The diagram in Figure 1 contains most of the classes and interfaces needed to create and execute analysis providers.

Figure 1. Diagram of classes and interfaces
image of diagram
Table 1: Description of classes and interfaces
Class or InterfaceDescription
AbstractAnalysisElementThe base class for all analysis elements. You will probably never use this directly, but it defines the characteristics that all analysis elements possess.
AbstractAnalysisProviderAn analysis provider is an element that defines a type of static analysis. Additionally, providers own one or more categories. For example, Java code review is an analysis provider.
DefaultAnalysisCategoryA category is a grouping concept to classify subcategories and rules, based on a common characteristic. For example, a Threading category contains a set of rules related to threading.
AbstractAnalysisRuleA rule is the workhorse of analysis. As you have seen in previous articles in this series, a rule accepts one or more analysis resources and performs an evaluation. Based on the outcome of this evaluation, results may be created.
AbstractAnalysisResultResults contain information about the outcome of a rule for a set of resources being analyzed. The result contains domain-specific data that must be rendered by a viewer. Results are not directly associated with a rule; rather, they are owners by an analysis history (as discussed next).

History framework

If you have used the code review feature from Rational Software Analyzer, then you have seen that each time it is executed a new historical scan entry is created in the analysis results view. This entry contains a list of all providers, categories, and rules that were defined in the analysis launch configuration, as well as a list of selected resources that were analyzed. Lastly, it contains a list of results produced during analysis. Collectively, this set of information is known as a history, and it resembles Figure 2.

Figure 2. History framework
image of workspace

If you examine the structure of the history facility closer (see Figure 3), you will notice that the AbstractAnalysisRule class uses an instance of an AnalysisHistory to store any results that is generates. When the analysis process starts, the AnalysisHistoryFactory is prompted to create a history, and this instance is populated with the list of analysis elements involved in the current process (primarily the list of selected rules). When you are writing a new analysis provider, it is not important how this happens; it is enough to know that your provider will receive this instance when the analysis starts.

Figure 3. Diagram of analysis elements
image of diagram

Basic process sequence

When users select sets of rules, they are unwittingly also selecting the owning categories and providers for those rules. When someone clicks the Analyze button, a new history instance is created, and the list of selected providers, categories, and rules is attached before the analysis process begins.

Internally, in the analysis framework, there is an AnalysisProviderManager class that manages all of the providers in the system. When the user starts to analyze the resources in the workspace, this class starts an asynchronous job for each enabled provider and calls its analyze() method.

Typically, the analyze() method in each provider simply iterates through its enabled categories and calls their matching analyze() methods. In turn, each category invokes the analyze() method of any enabled rules that it contains. Rules then do some work and generate results, along with any domain-specific data needed to view the result. All results are stored in the history instance that was created when the analysis began.

As you can see, this is a very simple process, but it is more than sufficient for performing any sort of static analysis. We have covered the basic groundwork, and this is enough to start writing a new provider.

Creating a provider from first principles

In this section, you will create a new analysis provider from the ground up. You will follow this exact process if you want to write an entirely new analysis loop for a specific domain. The process to do this is almost trivial, because you need to create only a provider class, a result class, and an extension.

Note:
If you do not have access to a ready-made parser for your language domain, such as Java development tools or Eclipse C/C++ Development Tools (CDT), you will need to write one. Java CC or ANTLR are typical tools for this task, but these are not required for the purposes of this article.

Creating the provider plug-in

In this example, you will simply replicate what the C/C++ analysis provider in Rational Software Analyzer does. The first item that we require is a plug-in project where our code and extension will reside.

  1. Select the File > New > Project menu option and, in the subsequent dialog, select Plug-in Project.
  2. Click Next, and in the Name field enter analysis.mycpp.
  3. Click Next again, and populate the form data (also shown in Figure 4):
Figure 4. Creating a new plug-in project
image of dialog box
  1. To complete the creation of the project, click Finish.

To build analysis tools, you need to add a few dependencies to the plug-in.

  1. Open the META-INF/MANIFEST.MF file in an editor, and select the Dependencies tab.
  2. In the Required Plug-ins list, add the plug-ins shown in Figure 5.
Figure 5. Add plug-ins
image of workspace

Creating parser classes

Before any provider can be built, the existence of some sort of domain-specific parser is required. For example, in the Java code review provider in Rational Software Analyzer, the CodeReviewResource class exists to manage all queries into Java source files. In that class, there was no need to invent a parser, because Eclipse already provides a complete DOM (Document Object Model) for Java that is accessible with calls into the JDT API.

For C and C++, we can be equally lazy, because Eclipse also provides an open source project called CDT (C/C++ Development Tooling) that includes a similar parser for the target language. We will not go into much detail about CDT here, but you can find everything you need on the CDT site, including API Javadocs. Because the details of CDT are well beyond the scope of this article, you will need to take a leap of faith at this point. The code in Listing 1 is the CodeReviewResource class that you will use for parsing C/C++ code. It is basically a thin API around CDT that simplifies the interface between it and your analysis provider.

Listing 1. CodeReviewResource class
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;
}
}

You will notice that the CodeReviewResource references a class named CodeReviewVisitor. This is another class that you need to write to parse C/C++ files with CDT. Like the JDT, the CDT DOM uses a visitor pattern to interact with its parse tree. Because you have specific requirements, you need to implement a new visitor for your needs. Again, we won't go into details of this class, but you can find documentation for this in the CDT Javadoc. Listing 2 shows the visitor class for this application.

Listing 2. Visitor class
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);
} 
}

Given that you are using CDT, you also have some new requirements:

First, the CDT feature must be installed into the Eclipse workbench:

  1. Go to the <a href="http://www.eclipse.org/cdt" target="_blank">CDT Web site</a>, and follow the installation instructions.

Next, you need to add an additional dependency to the plug-in manifest file:

  1. Open the manifest editor and add the org.eclipse.cdt.core plug-in to the list of required plug-in dependencies.
  2. You are also now working with resources, so also add the org.eclipse.core.resources plug-in as a dependency.

As noted, the CodeReviewResource class shown here is a bit of a black box; however, as you will see, the provider that you are writing does not actually interact with this class directly. In fact, because this is a specific API used to work with the domain-specific parser, only rules will ever communicate directly with this class. The provider's only duty is to create a new parser instance for each resource being analyzed and pass it down to the rules.

Creating a Result class

The final requirement before you can start writing the C++ code review provider is to define how you want results to look. From the provider's perspective, an analysis result can be loosely described as a container for domain-specific data, along with some basic access methods to manipulate it. For a C/C++ result, you will need to keep track of the resource where the result exists, as well as the start and end positions of the code involved. Because the provider that you are writing uses CDT, you can also track the CDT Abstract Syntax Tree (AST) node so that you can find the exact location later if necessary. The code in Listing 3 defines the class that you will use to represent a C/C++ result.

Listing 3. Class that represents a C/C++ result.
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 );
}
}

The result class shown here is relatively straightforward. It overrides the getLabel() method from the parent class to render a custom result label. But otherwise, it contains only basic domain-specific data.

In the CodeReviewResult class, we have purposely avoided using Eclipse markers, because there is already enough to understand without complicating it with text markers. However, if you want to write a production-grade C++ code review provider, then markers and annotations should be used to highlight problems in the Eclipse editor, as well as to help keep track of their locations.

You will notice that the result extends the AbstractAnalysisResult class, as all results should. If you really want to do extra work, you can implement the IAnalysisResult interface, instead, but you should only do this in rare situations where the abstract does not meet your needs.

Creating a basic provider class

With your new project and some up-front support classes defined, you can now create an analysis provider class.

  1. In the analysis.mycpp package of the plug-in project, create a new class named MyCppProvider. All providers must extend the AbstractAnalysisProvider class, which supplies the bulk of the provider functionality. Beyond this extension, all your provider class needs is an analyze() method.
  2. Modify your provider class to resemble the code in Listing 4.
Listing 4. Provider class modifications
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 ) {
}
}

This provider deals only with C and C++ files; therefore, you need to ignore other file types, because they may cause your parser to produce false results. Fortunately, the static analysis API includes support to simplify this in the AnalysisUtil class.

  1. Add the following code as the first lines of the provider analyze() method:
// 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 );

These lines build a subset list of file resources from the entire list passed to the provider, and it includes only files with extensions that are used by the C and C++ languages. To perform a code review on all of these files, you need to iterate through this list.

  1. Add the following code to the end of the analyze() method:
for( Iterator it = filteredResources.iterator(); it.hasNext(); ) {
CodeReviewResource codeReviewRes=new CodeReviewResource((IResource)it.next() );
setProperty( history.getHistoryId(), RESOURCE_PROPERTY, codeReviewRes );
}

This code iterates through each resource and creates an instance of the CodeReviewResource class (the C++ parser) that you created previously. This code also sets a property in the provider containing this instance.

Tip:
Analysis providers that extend AbstractAnalysisProvider own a property list that can be used to share information about any child categories and rules. Additionally, this can be used to share information between rules if required. The C/C++ analysis provider uses this facility to pass the current code review resource to each rule. Note that each analysis history contains its own property list. Therefore, there may be several property sets for any provider, which will be automatically removed when the user deletes a history from the analysis result view. This is the API for this facility:

public void setProperty(String historyId, String name, Object data) 
public Object getProperty(String historyId, String name)
public void removeProperty(String historyId, String name)

The provider also needs to pay attention to the progress monitor, because the user needs feedback and may opt to cancel the analysis process.

  1. Modify the analyze() method so that it looks like Listing 5.
Listing 5. Modifications to the analyze() method
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();
}

The basic task of a provider analyze() method is to send an "analyze" message to any of the categories that it owns. However, because the user may not have selected all categories, the provider needs to ensure that this message is sent only to those categories that the user has chosen. The solution to this problem is to have the provider iterate through its owned elements (which will always be limited to category elements) and call their analyze() methods.

  1. In the provider analyze() method, replace the "Do work here" comment with the following code:
for( AbstractAnalysisElement element : getOwnedElements() ) {
if( monitor.isCanceled() ) {
tearDown();
return;
}

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

Once again, you need to poll the progress monitor to ensure that use has not cancelled the analysis operation. To test if a category is enabled, you exploit a feature of the history instance. The AnalysisHistory.containsAnalysisElement() method accepts any analysis element and tests to see whether it was selected by the user before the static analysis process began. If the owned category appears in the history, then the provider can call its analyze() method.

The completed analysis provider class for C/C++ code review should resemble the code in Listing 6. The only other addition is the call at the end of the class to remove the property that you set, which is just a precaution to ensure that memory does not leak away unexpectedly.

Listing 6. Completed analysis provider class for C/C++ code review
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();
}
}

Creating a provider plug-in extension

With the provider class now complete, you face a new problem, because you now need to plug this new functionality into Eclipse. The solution to this is simple: You define an extension point to allow the analysis facility to find the C++ analysis capabilities.

  1. Open the plug-in manifest file, and select the Extensions tab.
  2. Click the Add button, and, in the New Extension dialog, select the com.ibm.rsaz.analysis.core.analysisProvider extension point. This extension point is used to identify new analysis providers.
  3. The analysis provider extension should now be listed in the All Extensions list. Right-click this entry, and select New > analysisProvider, as shown in Figure 6.
Figure 6. Defining the extension point
image of menu

Select the analysisProvider entry that is added, and complete the form to resemble the screen in Figure 7:

  • Notice that the Class field contains the qualified name of the provider class created in the previous section.
  • The ID field should be an identifier that is sufficiently unique (the qualified class name works well for this, but it can be any unique string).
  • The Label field contains the string that will be visible to the user when the analysis configuration dialog is visible. Ideally, the label will be a localized string retrieved from the plugin.properties file, but in this case, a hard-coded string will be used for simplicity.

We will discuss the remaining fields later. For now, leave them blank as shown in Figure 7.

Figure 7. Completing the extension element details
image of workspace

Believe it or not, this is all you need to do to plug a provider into the static analysis framework. If you start a runtime workbench from your Eclipse development environment, you can verify that your new provider is now available for use (see Figure 8).

Figure 8. Verifying the analysis provider
image of workspace

Of course, at this point, the provider is useless, because it does not define any categories or rules. But it is definitely a true analysis rule engine. So now, let's give it a purpose.

Adding an analysis category

The first step toward a useful analysis provider is to add one or more categories. All categories must extend the DefaultAnalysisCategory class, which you can use directly for almost any category that you will ever create. However, you can also extend this class and override its method to provide additional functionality if needed. Because the categories for C++ do nothing special, you will use the default class. But if you examine this class, you will see that, like an analysis provider class, it has an analyze() method defined, as shown in Listing 7.

Listing 7. DefaultAnalysisCategory class
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);
}

}
}
}
}
}

One point of interest in this code is the realization that a category can own either rules or other categories (they can be nested). For this reason, before the analyze() method of any owned element can be called, the category needs to know that it is a rule or a category. It uses the AbstractAnalysisElement.getElementType() method to determine this.

Another notable feature here is that before executing a rule, the code starts a timer and terminates it when the rule is complete. This is used to track the total execution time of each rule.

If you decide to write your own custom category and implement a new analyze() method, you need to be aware of these points to ensure that your own category operates the same way. Otherwise, you will experience some unexpected behavior. The easiest way to avoid this is to clone the default category analyze() method and modify it to suit your needs.

After you have a category class (DefaultAnalysisCategory in this case), you need to associate it with your provider.

  1. To accomplish this, you need to once again return to the plug-in manifest and add a new extension. This time: com.ibm.rsaz.analysis.core.analysisCategory.
  2. Right-click this extension, add a new analysisCategory, and populate the resulting form shown in Figure 9.
Figure 9. Associating the category class with the analysis provider
image of workspace

Notice that the Provider field is populated with the unique identifier for the provider created previously. This identifies the new category at the top level directly owned by the provider. When creating subcategories, the provider field should be left empty. Instead, the Category field in the form can be populated with the ID of a parent category.

Adding a rule

Rule creation was largely covered in Part 2 of this series (the C++ provider rule structure varies little from what was discussed for Java), so we won't belabor the point. As you might expect, adding a new rule is a simple matter of creating a rule class and adding an extension to the plug-in.

The rule class in this example will look for C++ code that declares multiple variables on the same line. For example:

int i,j,k;
  1. In the plug-in's src folder, create a new class named RuleMutipleDeclarations.
  2. Add the code shown in Listing 8 this class.

Listing 8. Adding a rule

Listing 8. Code to add to the RuleMutipleDeclarations class
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 );
}
}
}
}
}

We won't go into great detail about how this works, but in basic terms, it first gets a reference to the CodeReviewResource class, which, as you remember, is the parser created by the provider for the C++ class being analyzed. Then the class uses some of the parser API to get a list of declarations. Each declaration is tested to verify that the number of declarations is limited to 1 (one). If not, a result is generated by using additional API from the CodeReviewResource class that you created earlier.

  1. The final step in the process is to add the rule extension by using the com.ibm.rsaz.analysis.core.analysisRule extension point. Make your extension look like the one shown in the illustration in Figure 10. (Notice that the category field is populated with the unique identifier of the category created in the previous section.)

Other aspects of rules discussed in Part 2 of this series of articles apply here, as well. For example, you can implement support for automatic code fixing, user Help, and so forth.

Figure 10. Adding the rule extension
image of workspace

Viewing results

Up to this point, you have no way to display results that are captured in the analysis results view. This is the responsibility of the analysis viewer, and the Rational Software Analyzer framework supplies the IAnalysisViewer interface and an abstract class called AbstractAnalysisViewer to manage this. Recall that the results created by C++ rules contain a lot of useful information, including the CDT node information, line number, and more. When you want to view a result, you can exploit this information to position the C++ source file in the text editor and highlight the problem text.

Before you can implement an analysis viewer, a little understanding is necessary. Viewers can be attached to any analysis element for the purposes of rendering rule result data. For example, a viewer does not need to be associated with a rule; providers and categories can reference a viewer, too. In this case, the viewer will apply to any rules that fall within the ownership chain of the provider or category. Consider the following rule structure:

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

Assume that a viewer is associated with Provider A. When the getViewer() method calls Rule C, D or F, they will first check their own viewer references. If no viewer is found, the rules will ask their owning categories for viewers. In this scenario, the categories have no assigned viewers; therefore, they ask their owning providers, which return the viewers that you assigned. Now further assume that a different viewer is assigned to Rule C. In this case, all rules but C will use the same default viewer on the provider, but C will use the one that it was assigned.

The fallback scheme permits a single viewer to handle all rule result rendering, yet allows alternate viewers to be attached anywhere in the tree to override the default behavior. With this understanding, let's take a look at a viewer example.

Listing 9. Viewer code example
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();
}
}

There are a couple of things to observe in this example:

  • The viewer class implements the IAnalysisViewer interface, which forces it to implement the showView() method. In simple terms, this viewer code extracts the resource from the result and creates a qualified path for it. This path is passed to a text editor for display, and, finally, the editor is positioned so that the CDT node is visible in the editor and is selected.
  • To improve the user experience, all of this work is done within an Eclipse job.

To enable this viewer class, you need to add another extension to the plug-in file:

  1. Open the plugin.xml file, and select the Extensions tab.
  2. Add a new extension for com.ibm.rsaz.analysis.core.analysisViewer and populate the form similar to the one shown in Figure 11.
Figure 11. Enabling the viewer class
image of workspace
  1. Next, you can associate the viewer with the provider by entering the viewer's unique identifier in the supplied field in the provider (see Figure 12).
Figure 12. Associating the viewer with the provider
image of workspace

Finally,you need to resolve the compiler errors in the viewer class. This means additional plug-in dependencies need to be applied to support editing in the Eclipse IDE.

  1. Select the Dependencies tab, and add org.eclipse.ui.workbench.texteditor and org.eclipse.ui.ide to the list of required plug-ins.
  2. To test the viewer, run the C++ analysis again (on source code that produces results).
  3. Double-click on a result in the analysis result view. An editor will open in the Eclipse workbench containing the highlighted error from the appropriate class.

External static analysis engines

One aspect of analysis providers that has been ignored is the ability to integrate existing analysis engines. Given that this involves close interactions with a domain-specific engine, we have not discussed it here. However, assuming that there is API access to the engine, it should be possible to plug it into the Rational Software Analyzer framework. In this case, the provider, categories, and rules may simply synthesize functionality that really exists in the external engine. For example:

  • If the external engine already supports categories, override the $getOwnedElements() method in the provider to return a list of categories wrapped in a DefaultAnalysisCategory class.
  • If rules exist, override this same getOwnedElements() method in the category classes to return a list of AbstractAnalysisRule instances that mirror what the real analysis engine supports.

Summary and what's next

In this article, we have covered most of the APIs and requirements to build a new analysis provider from the ground up, including the definition of a category and rule. We have also taken a brief look at how to define an analysis result class and given a bit of an overview of the parser. Fundamentally, all providers will be structured similarly to this one, regardless of the language being analyzed.

In the next article in this series we will look at way to access Rational Software Analyzer functionality from a command line to help integrate the tool into any build system.

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Rational software on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Rational, DevOps
ArticleID=302472
ArticleTitle=Static analysis IBM Rational Software Analyzer: Part 4. Integrating your own analysis tools
publish-date=04292008