Eclipse Article

TPTP Static Analysis Tutorial Part 3

Integrating Your Own Analysis

The Eclipse Test & Performance Tools Platform (TPTP) is a powerful tool for evaluating the overall performance and quality of Java source code.  In addition to well-known features for measuring code performance, TPTP now includes a core framework for handling static analysis functions.  The framework for building static analysis providers was supplied in TPTP 4.1, but in more recent versions, complete code review providers for C/C++ and Java have been made available. In the latter case, a set of over 70 rules is included.  This third article in the static analysis series describes how to integrate a new analysis engine in the analysis user interface using the API supplied in the framework.

By Steve Gutz, IBM
April 24, 2006


Introduction

In Parts 1 and 2 if 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 provider supplied with the TPTP static analysis feature.  So far most aspects of the analysis API have been largely hidden from you, but if you are reading this third article then you must have a need to integrate some new form of analysis into the workbench, and to accomplish this feat you need to dive a lot deeper.

In Part 3 we will try to ease into the process of integrating new forms of analysis.  There won’t be anything too difficult here but it is more involved than adding a Java code review rule.  By the time you are finished this article you will have a new analysis provider built from the ground up and seamlessly integrated into the TPTP analysis framework.  So let’s get started!

 

Architectural Overview

In previous articles we have skirted most of the real details of the analysis architecture, opting instead to focus 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 we have talked about in this series, every effort to reduce the barrier-to-entry has been taken, 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 following diagram contains most of the interfaces needed to create and execute analysis providers.

 

Interface

Description

IAnalysisElement

The base interface for all analysis elements.  You will probably never use this directly, but it does define the characteristics that all analysis elements have.

IAnalysisProvider

An 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.

IAnalysisCategory

A category is a grouping concept to classify sub-categories and rules based on a common characteristic.  For example a “Threading” category will contain a set of rules relating to threading concerns.

IAnalysisRule

A rule is the work horse of analysis.  As we 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.

IAnalysisResult

Results 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.  Note that results are not directly associated with a rule, rather they are owners by an analysis history (which will be discussed next).

 

History Framework

If you have used the code review feature from TPTP then you have seen that each time it is executed a new entry is created in the analysis results view.  This entry contains a list of all providers, categories, rules that were defined in the analysis launch configuration along with a list of results produced.  Collectively this set of information is known as a history, and resembles the following:

If you examine the structure of the history facility closer (see the following diagram), you will notice that the IAnalysisRule interface uses an instance of an AnalysisHistory to store any results 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 (primarily the list of selected rules) involved in the current process.  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.

Basic Process Sequence

When the user selects a set of rules, he is unwittingly also selecting the owning categories and provider for those rules.  Once the “Analyze” button is pressed, 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 a 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() method.  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 ground work and this is enough for us to start writing a new provider.

A Provider From First Principles

In this section we 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 since you need to create only a provider class, a result class and an extension.  Note that if you do not have access to a ready-made parser for your language domain (such as JDT or CDT) you will need to write one.  JavaCC or ANTLR are typical tools for this task, but for the purposes of this article we will not require these.

 

The Provider Plug-in

 

In this example we will simply replicate what TPTP’s  C/C++ analysis provider does, and the first item we require is a plug-in project where our code and extension will reside.  Select the File->New->Project… menu option and in the subsequent dialog select “Plug-in Project”. Click Next> and in the name field enter “analysis.mycpp”.  Click Next> again and populate the form data as follows:

 

 

To complete the project creation, click the Finish button.

 

In order to build analysis tools you need to add a few dependencies to the plug-in.  Open the META-INF/MANIFEST.MF file in an editor and select the “Dependencies” tab.  In the “Required Plug-ins” list add the following plug-ins:

 

 

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 TPTP, the CodeReviewResource class exists to manage all queries into Java source files.  In that class there was no need to invent a parser since Eclipse already provides a complete DOM (Document Object Model) for Java in the JDT plug-ins.

 

For C and C++ we can be equally lazy since Eclipse also provides an open source project called CDT (C/C++ Development Tooling) that includes a similar parser for our target language.  We will not go into much detail of CDT here, but on the CDT site (http://www.eclipse.org/cdt) you will find everything you need including API JavaDocs.

 

Since the details of CDT are well beyond the scope of this document, you will have to take a leap of faith at this point.  The following code is the CodeReviewResource class we will use for parsing C/C++ code.  It is basically a thin API around CDT that simplifies the interface between it and our analysis provider.

 

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 org.eclipse.tptp.platform.analysis.core.logging.Log;

import org.eclipse.tptp.platform.analysis.core.rule.IAnalysisRule;

 

 

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( IAnalysisRule 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( IAnalysisRule 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 we need to write in support of parsing C/C++ files with CDT.  Like JDT, the CDT DOM uses a visitor pattern to interact with its parse tree.  Since we have specific requirements, we need to implement a new visitor for our needs.  Again we won’t go into details of this class, but you can find documentation for this in the CDT JavaDoc.  The visitor class for our application is as follows:

 

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);

       }     

}

 

Since we are using CDT we also have some new requirements.  First the CDT feature must be installed into the Eclipse workbench.  To do this go to the CDT web site and follow the installation instructions.

 

Next we need to add an additional dependency to the plug-in manifest file.  Open the manifest editor and add the “org.eclipse.cdt.core” plug-in to the list of required plug-in dependencies.  We 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 we are writing does not actually interact with this class directly.  In fact since 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

 

One final requirement needed before we can start writing the C++ code review provider is to define how we 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 we will need to keep track of the resource where the result exists along with the start and end position of the code involved.  Since the provider we are writing uses CDT we can also track the CDT Abstract Syntax Tree (AST) node so we can find the exact location later if needed.  The following code defines the class we will use to represent a C/C++ result:

 

package analysis.mycpp;

 

import java.io.File;

 

import org.eclipse.cdt.core.dom.ast.IASTNode;

import org.eclipse.tptp.platform.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 in order to render a custom result label, but otherwise contains only basic domain-specific data.

 

In the CodeReviewResult class we have purposely avoided using Eclipse markers – 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 help keep track of their locations.  If you want ideas how to do this examine the CodeReviewResult class in the TPTP Java Code Review provider source code.

 

You will notice that our result extends the AbstractAnalysisResult class, as all results should.  Note that if you really want to do extra work you can implement the IAlysisResult 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 our new project and some up-front support classes defined, we can now create an analysis provider class.  In the “analysis.mycpp” package of the plug-in project create a new class named “MyCppProvider”.  All providers must implement the IAnalysisPorivder interface; however, the analysis framework provides an abstract class that implements this interface for you and performs most of the work you need to do in every provider.  Open your provider class in an editor and make it extend the AbstractAnalysisProvider class.  If you use the abstract all your provider class needs is an “analyze()” method.  Modify your provider class to resemble the following:

 

package analysis.mycpp;

 

import org.eclipse.core.runtime.IProgressMonitor;

import org.eclipse.tptp.platform.analysis.core.history.AnalysisHistory;

import org.eclipse.tptp.platform.analysis.core.provider.AbstractAnalysisProvider;

 

public class MyCppProvider extends AbstractAnalysisProvider {

 

      public void analyze( IProgressMonitor monitor, AnalysisHistory history ) {

      }

}

 

Since our provider deals only with C and C++ files we need to ignore other file types since they may cause our parser to produce false results.  Fortunately the static analysis API has some support to simplify this using in the AnalysisUtil class.  As the first line of the provider analyze() method add the following:

 

List filteredResources = AnalysisUtil.getFilteredResources(

getResources(), "c,cpp" );

 

This line builds a subset list of file resources from the entire list passed to the provider and includes only files with a “.c” or “.cpp” extension.  We know that to perform a code review on all of these files so we need to iterate through this list.  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) we created previously.  This code also sets a property in the provider containing this instance. 

 

Tip ► Providers that extend AbstractAnalysisProvider own a property list that can be used to share information 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 so there may be several property sets for any provider – which will be automatically removed when the users deletes a history from the analysis result view. The API for this facility is:

 

            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 since the user needs feedback and may opt to cancel the analysis process.  Modify the analyze() method so it looks like this:

 

public void analyze( IProgressMonitor monitor, AnalysisHistory history ) {

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

List filteredResources = AnalysisUtil.getFilteredResources(getResources(), "c,cpp" );

for( Iterator it = filteredResources.iterator(); it.hasNext(); ) {

              CodeReviewResource codeReviewRes=new CodeReviewResource((IResource)it.next() );

              setProperty( history.getHistoryId(), RESOURCE_PROPERTY, codeReviewRes );

             

if( monitor.isCanceled() ) {

                     return;

              }

 

              // Do work here

 

              monitor.worked( 1 );

       }

       monitor.done();

}

 

The basic task of a provider analyze() method is to send an “analyze” message to any of the categories it owns.  However since 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.  In the provider analyze() replace the “Do work here” comment with the following code:

 

for( Iterator it2 = getOwnedElements().iterator(); it2.hasNext(); ) {

       if( monitor.isCanceled() ) {

              return;

       }

 

       // Get the unbquitous category

       IAnalysisCategory category = (IAnalysisCategory)it2.next();

       if( history.containsAnalysisElement( category ) ) {

              category.analyze( history );

       }

}

 

Once again we need to poll the progress monitor to ensure that use has not cancelled the analysis operation.  To test if a category is enabled we exploit a feature of the history instance.  The AnalysisHistory.containsAnalysisElement() method accepts any analysis element and tests to see if 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 only other requirement of a provider is to notify any listening process on a regular basis that our provider is working.  For example the analysis result viewer listens for analysis activity methods so it can update the user interface periodically.  For our C++ analysis provider we will allow the UI to update anything we are finished processing a category.  To do this enter the following line to the end of the category iterator loop in the analyze() method:

 

// Tell anyone who cares that we have finished examining 1 file

getProviderManager().notifyAnalysisListeners( this );

 

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

 

package org.eclipse.tptp.platform.analysis.codereview.cpp;

 

import java.util.Iterator;

import java.util.List;

 

import org.eclipse.core.resources.IResource;

import org.eclipse.core.runtime.CoreException;

import org.eclipse.core.runtime.IConfigurationElement;

import org.eclipse.core.runtime.IProgressMonitor;

import org.eclipse.tptp.platform.analysis.core.AnalysisUtil;

import org.eclipse.tptp.platform.analysis.core.category.IAnalysisCategory;

import org.eclipse.tptp.platform.analysis.core.history.AnalysisHistory;

import org.eclipse.tptp.platform.analysis.core.provider.AbstractAnalysisProvider;

 

public class CodeReviewProvider

extends AbstractAnalysisProvider

{

       public static final String RESOURCE_PROPERTY = "resource"; //$NON-NLS-1$

      

       /**

        * 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

        *

        * @throws CoreException

        */

       public void analyze( IProgressMonitor monitor, AnalysisHistory history ) {

           // Obtain the list of valid resource

              List filteredResources = AnalysisUtil.getFilteredResources(

getResources(), "c,cpp" );

 

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

             

              // Iterat through each selected resource file

              for( Iterator it = filteredResources.iterator(); it.hasNext(); ) {

                     if( monitor.isCanceled() ) {

                           return;

                     }

 

                     CodeReviewResource codeReviewRes =

new CodeReviewResource( (IResource)it.next() );

                     setProperty( history.getHistoryId(), RESOURCE_PROPERTY, codeReviewRes );

             

                     for( Iterator it2 = getOwnedElements().iterator(); it2.hasNext(); ) {

                           if( monitor.isCanceled() ) {

                                  return;

                           }

 

                           // Get the unbquitous category

                           IAnalysisCategory category = (IAnalysisCategory)it2.next();

                           if( history.containsAnalysisElement( category ) ) {

                                  category.analyze( history );

                           }

                     }

      

                     // Tell anyone who cares that we have finished examining 1 file

                     getProviderManager().notifyAnalysisListeners( this );

      

                     monitor.worked( 1 );

              }

       

              removeProperty(history.getHistoryId(), RESOURCE_PROPERTY);       

              monitor.done();

       }

}

 

Creating a Provider Plug-in Extension

 

With the provider class now complete we face a new problem because we now need to plug this new functionality into Eclipse.  The solution to this is simple – we simply need to define an extension point to allow the analysis facility to find the C++ analysis capabilities.  Open the plug-in manifest file and select the “Extensions” tab.  Click the “Add…” button and select the “org.eclipse.tptp.platform.analysis.core.analysisProvider” extension point in the “New Extension” dialog. This extension point is used to identify new analysis providers.

 

You should now have the analysis provider extension listed in the “All Extensions” list.  Right-click this entry and select New->analysisProvider as shown in the following screen

 

 

 

Select the analysisProvider entry that is added and fill the form to resemble the following screen.  Note 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 our case a hard-coded string will be used for simplicity.

 

We will discuss the remaining fields later.  For now leave them blank.

 

 

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 the use.

 

 

Of course at this point the provider is useless since it does not define any categories or rules, but it is definitely a true analysis rule engine.  So 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 implement the IAnalysisCategory interface; however, following the trend to make integration easy, the API helps.  You will find a DefaultAnalysisCategory class (there is no AbstractAnalysisCategory class) that you can use directly for almost any category you will ever create. However you can also extend this class and override its method to provide additional functionality if needed.  Since the categories for C++ do nothing special we will use the default class, but if you examine this class you will see that like a provider it has an analyze() method defined as follows:

 

public void analyze( AnalysisHistory history ) {

       // Analyze any nested categories

       List categories = getOwnedElements();

       if( categories != null ) {

              for( Iterator it = categories.iterator(); it.hasNext(); )

              {

                     IAnalysisElement element = (IAnalysisElement)it.next();

                     if( history.containsAnalysisElement( element ) ) {

                            element.addHistoryResultSet( history.getHistoryId() );

 

                            if( element.getElementType() == AnalysisConstants.CATEGORY_ELEMENT_TYPE ) {

IAnalysisCategory category = (IAnalysisCategory) element;

                                  try {

                                                category.analyze( history );

                                  } catch (Exception e) {

                                                Log.severe( CoreMessages.bind(

CoreMessages.execute_analyzeCategory_failure,

category.getLabel()), e );

                                  }

                           }

                           else if( element.getElementType() == AnalysisConstants.RULE_ELEMENT_TYPE ) {

                                  IAnalysisRule rule = (IAnalysisRule)element;

                                  try {

                                         // Start a timer to time the rule execution

                                         AnalysisHistoryElement historyElement =

                                                history.getHistoryElement( rule );

                                         historyElement.startElapsedTimer();     

 

                                         rule.analyze( history );

                                               

                                         // Stop the timer

                                         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 it is a rule or a category.  It uses the IAnalysisElement.getElementType() method to determine this.

 

Another notable feature here is 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 and 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.

 

Once you have a category class (DefaultAnalysisCategory in our case), we need to associate it with our provider.  To accomplish this we need to once again return to the plug-in manifest and add a new extension – this time “org.eclipse.tptp.platform.analysis.core.analysisCategory”.  To this extension right-click and add a new analysisCategory and populate the resulting form as follows:

 

 

Note 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 sub-categories the provider field should be left empty and 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 – our 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 rules is a simple matter of creating a rule class and adding an extension to the plug-in.

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

      int i,j,k;

Create a new class in the plug-in’s src folder named “RuleMutipleDeclarations” and add the following code to the this 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 org.eclipse.tptp.platform.analysis.core.history.AnalysisHistory;

import org.eclipse.tptp.platform.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, 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 obtain a list of declarations.  For each declaration a test is made to verify that the number of declarators is limited to 1.  If not a result is generated using additional API from the CodeReviewResource class we created earlier.

The final step in the process is to add the rule extension using the org.eclipse.tptp.platform.analysis.core.analysis.rule extension point.  Make your extension look like the one shown in the following illustration (Note 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, help, etc.

Viewing Results

 

Up to this point we have no way to display results that are captured in the Analysis Results view.  This is the responsibility of the analysis viewer, and the TPTP framework supplies the IAnalysisViewer interface and an abstract class 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, etc.  When we want to view a result we can exploit this information to position the C++ source file in the text editor, and highlight the problem text.

 

Before we can implement an analysis viewer a little understanding is needed.  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 a viewer is associated with Provider A.  When the getViewer() method is called Rule C, D or F, they will first check their own viewer reference.  No viewer is found, the rules will ask their owning category for a viewer.  In this scenario the categories have no assigned viewer, so they ask their owning provider, which returns the viewer we assigned.  Now further assume rule C is assigned a different viewer.  In this case all rules but C will use the same default viewer on the provider, but C will use the one 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.

 

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.tptp.platform.analysis.core.logging.Log;

import org.eclipse.tptp.platform.analysis.core.result.IAnalysisResult;

import org.eclipse.tptp.platform.analysis.core.viewer.IAnalysisViewer;

import org.eclipse.ui.PlatformUI;

import org.eclipse.ui.ide.IDE;

import org.eclipse.ui.texteditor.ITextEditor;

 

 

public class CodeReviewViewer

       implements IAnalysisViewer

{

       public void showView( final IAnalysisResult 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 );  

editor.selectAndReveal(

       specificResult.getStartPositionSelection(),

specificResult.getLengthSelection() );

                                            

                                             } catch ( Exception e ) {

                                                Log.severe( "Error loading analysis viewer", e );

                                             }

                                         }

                                  }

                           });

                           return Status.OK_STATUS;

                     }

              };

              job.schedule();

       }

}

 

There are a couple of observations from this example.  First, 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 such that the CDT node is visible in the editor and is selected.  Finally to improve the user experience, all of this work is done inside an Eclipse job.

 

To enable this viewer class, we need to add another extension to the plug-in file.  Open the plugin.xml file and select the “Extensions” tab.  Add a new extension for org.eclipse.tptp.platform.analysis.core.analysisViewer and populate the form according to the following:

 

 

Next we can associate the viewer with the provider by entering the viewer’s unique identifier in the supplied field in the provider:

 

 

Finally, we 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.  Select the “Dependencies” tab and add “org.eclipse.ui.workbench.texteditor” and “org.eclipse.ui.ide” to the list of required plug-ins.

 

To test the viewer, run the C++ analysis again (on source code that produces results).  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.  Since this involves close interactions with a domain-specific engine, we have not discussed it here; however, assuming there is API access to the engine, it should be possible to plug it into the TPTP analysis 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, IAnalysisCategory interface.  If rules exist, override this same getOwnedElements() method in the category classes to return a list of IAnalysisRule implementations that mirror what the real analysis engine supports.

 

Conclusion

In this article we have covered most of the API’s 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 had a bit of an overview of the parser.  Fundamentally all providers will be structured similar to this one regardless of the language being analyzed.

 

In the next article in this series we will look at analysis data exporters and report generation using the Eclipse BIRT tools.