|
Eclipse Article |

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
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!
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.
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). |
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.

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

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.
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.
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();
}
}
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!
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.
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.
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.
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.
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.