How to Write a Simple Code Analyzer
Using NetBeans™ C/C++ Language Model API API

This tutorial will show you how easy you can extend NetBeans C/C++ functionality using the Language Model API.

You can read an overview of this API in the article NetBeans™ C/C++ Language Model API Overview and find a javadoc reference of the API by this link: NetBeans™ C/C++ Language Model API Reference

We are going to create a module that will check C++ source code for complying a very simple rule. The rule reads as follows: the destructor should always be virtual. You can find a lot of very usefil rules in the "Effective C++" by Scott Meyers and other books of the same aughtor. The information about the books is available at http://www.aristeia.com/books_frames.html

Well, the rule as we stated it is a bit simplified - if your class isn't supposed to be extended, it does not matter, whether the destructor is virtual or not. But we'll use this simplified rule to not dive too deep into details.

Below we'll describe the creation of the plugin step by step.

Creating Netbeans plug-in Module and Action

This part describes the creation of the Netbeans plug-in module and action. If it's sounds trivial, just skip it. The only thing you should know in this case is that you should add two modules to the dependencies: C/C++ Code Model API (org.netbeans.modules.cnd.api.model) and C/C++ Code Model Utilities (org.netbeans.modules.cnd.modelutil). Note that, since tha API is still experimental, you have to check "Show non-API modules" check box and add an implementation dependencies to these modules.

  1. Choose File > New Project. In the New Project wizard, select NetBeans Plug-in Modules under Categories and Module under Projects. Click Next.
  2. Select module location. Type the name of the module (for example, "CodeAnalyzer"). Click Finish.
  3. We will need certain dependencies in this project. In the Projects window, select Project Properties from context menu, select Libraries note, and press Add button. C/C++ modules APIs are under development, so you will need to select Show Non-API Modules in the dialog box to see them in the Module list. Add the following libraries:
    • C/C++ Code Model API (org.netbeans.modules.cnd.api.model)
    • C/C++ Code Model Utilities (org.netbeans.modules.cnd.modelutil)
    • Datasystems API (org.openide.loaders)
    • File System API (org.openide.filesystems)
    • I/O APIs (org.openide.io)
    • Nodes API (org.openide.nodes)
    • Project API (org.netbeans.modules.projectapi)
    • Utilities API (org.openide.util)
    You'll now have to specify that the dependency on "C/C++ Code Model API" and "C/C++ Code Model Utilities" modules is an implementation dependency. To do this, select each of the two modules in the libraries list, press Edit button, and check "Implementation Version" check box.
  4. We will add an action that is enabled when a project is selected; this action will perform a code analysis. To add an action, in Project Window, select New > Action from the context menu. Select "Conditionally Enabled", "Project" in Cookie Classes drop-down box and "User may Select Multiple Nodes" radio button:

    Press next. Select "Tools" in Category. Leave the default "Global Menu Item" check box checked, and select "Tools" in "Menu" drop-down. Press Next. Enter your action class name (for example, "AnalyzeAction") and display name (for example, "Analyze Code")

Now your action is ready. You may try launching your project. You will see the "Analyze Code" action in the "Tools" menu.

Creating a Very Simple Analyzer

We will now write an analyzer - a class that analyzes C++ code and creates a list of issues found.

Issue class

Let us create a simple class that represents a single found issue with source code. Since we work with Language Model, let us talk in its terminology. We will use a CsmOffsetable interface to represent an element the issue is about. The CsmOffsetable interface represents a C/C++ language construct that occupy a continuos extent in source or header file.


    import org.netbeans.modules.cnd.api.model.CsmOffsetable;
    
    /**
     * An issue - a simple class that represents
     * a single issue found in code
     */
    public class Issue {

            private String description;
            private CsmOffsetable element;

            public Issue(CsmOffsetable element, String gescription) {
                this.description = gescription;
                this.element = element;
            }

            /** Returns the description of the issue */
            public String getDescription() {
                return description;
            }

            /** Returns an element this issue is about */
            public CsmOffsetable getElement() {
                return element;
            }
    }    

An Analyzer Class

Let us create a Analyzer class that performs code analysis. It analyzes a project and creates a list of issues found. So the key methods are two: getIssues() and analyzeProject(). The Analyzer works in terms of Language Model. So we pass an instance of CsmProject to the analyzeProject() method.

The algorythm then drills down through the project code hierarchy, searches for classes ( CsmClass instances) and analyzes found classes.


    /**
     * Analyzes project code.
     * Fills a collection of issues found.
     */
    public class Analyzer {

        /** A collection of issues to fill */
        Collection<Issue> issues = new ArrayList<Issue>();

        /** Returns a list of issues found */
        public Collection<Issue> getIssues() {
            return issues;
        }

        /** The main entry point. Analyzes a project. */
        public void analyzeProject(CsmProject project) {
            // The project tree root is it's global namespace
            analyzeNamespace(project.getGlobalNamespace());
        }

        /** Analyzes the given namespace */
        private void analyzeNamespace(CsmNamespace namespace) {
            // go through this namespace declarations
            analyzeDeclarations(namespace.getDeclarations());
            // go through all nested namespaces
            for( CsmNamespace child : namespace.getNestedNamespaces() ) {
                analyzeNamespace(child);
            }
        }

        /** Analyzes the given collection of declarations */
        private void analyzeDeclarations(Collection<CsmOffsetableDeclaration> declarations) {
            for( CsmDeclaration decl : declarations ) {
                analyzeDeclaration(decl);
            }
        }

        /** Analyzes the given declaration */
        private void analyzeDeclaration(CsmDeclaration decl) {
            // Don't use instanceof - use CsmKindUtilities instead!
            if( CsmKindUtilities.isClass(decl)) {
                analyzeClass((CsmClass) decl, issues);
            }
            if( CsmKindUtilities.isNamespaceDefinition(decl)) {
                CsmNamespaceDefinition namespaceDefinition = (CsmNamespaceDefinition) decl;
                analyzeDeclarations(namespaceDefinition.getDeclarations());
            }
        }

        /** Analyzes the given class */
        private void analyzeClass(CsmClass cls, Collection<Issue> issues) {
            // go through the class members
            for( CsmMember mem : cls.getMembers() ) {
                if( CsmKindUtilities.isDestructor(mem) ) {
                    CsmMethod dtor = (CsmMethod) mem;
                    if( ! dtor.isVirtual() ) {
                        issues.add(new Issue(dtor, "Class "+ cls.getName() + " has non-virtual destructor"));
                    }
                }
            }
        }
    }

Joining Analyzer and AnalyzeAction

Now we need to call an Analyzer from our AnalyzeAction. First, we should warn you of extensive use of the Language Model API in the UI thread. The Language Model API method can be costly, so you should always call them from a separate thread:


    protected void performAction(final Node[] activatedNodes) {
	RequestProcessor.getDefault().post(new Runnable() {
	    public void run() {
		process(activatedNodes);
	    }
	});
    }
    
    private void process(Node[] activatedNodes) {
        // TODO: write the logic here
    }

Now we have an Analyzer class that works in terms of Language Model (i.e. expects an instance of CsmProject as a parameter). How to find the project to analyze? It is quite staightforward. Use Lookup for getting a NetBeans project; then use CsmModel.getProject() to find the element of Language Model that corresponds to the NetBeans project:


    private void process(Node[] activatedNodes) {
	Analyzer analyzer = new Analyzer();
	for (int i = 0; i < activatedNodes.length; i++) {
	    Project project = activatedNodes[i].getLookup().lookup(Project.class);
	    if( project != null ) {
                CsmProject csmProject = CsmModelAccessor.getModel().getProject(project);
		if (csmProject != null) {
		    analyzer.analyzeProject(csmProject);
		}
	    }
	}
	Collection<Issue> issues = analyzer.getIssues();
	// TODO: write the code to display issues
    }

Displaying output

Now there is the onlything left: displaying the isses. There are different approaches. You may create a TopComponent of your own, or use the NetBeans Task List API, or just use the NetBeans I/O APIs. We will use the latter, as it is the most straightforward.

Anyhow neither AnalyzeAction nor Analyzer should know how to display iusses. We will create a separate class that is responsible for this - IssuesDisplayer.

The IssuesDisplayer prints each issue to the Output Pane. It also associates an OutputListener with each line. When user control-clicks the line, the listener opens the correspondent element in editor.

 
    public class IssuesDisplayer {

        public static final Logger LOG = Logger.getLogger("org.yourorghere.codeanalyzer");

        public void display(Collection<Issue> issues) {
            if (!issues.isEmpty()) {
                InputOutput io = IOProvider .getDefault().getIO("Code Analyzer Results", false);
                io.select(); //Tree tab is selected
                OutputWriter writer = io.getOut();
                try {
                    writer.reset();
                    for (Issue issue : issues) {
                        final CsmOffsetable element = issue.getElement();
                        StringBuilder sb = new StringBuilder();
                        Formatter formatter = new Formatter(sb);
                        formatter.format("Warning %s:%d %s\n", element.getContainingFile().getAbsolutePath(), element.getStartPosition().getLine(), issue.getDescription());
                        writer.println(sb.toString(), new OutputListener() {
                            public void outputLineSelected(OutputEvent ev) {}
                            public void outputLineCleared(OutputEvent ev) {}
                            public void outputLineAction(OutputEvent ev) {
                                CsmUtilities.openSource(element);
                            }
                        });
                    }
                }
                catch( IOException ex ) {
                    LOG.log(Level.SEVERE, "error when filling output window\n {0}", new Object[] { ex }); // NOI18N
                }
            }
        }
    }      

Now call the IssuesDisplayer from the process() method shown above:

 
    private void process(Node[] activatedNodes) {
	Analyzer analyzer = new Analyzer();
	for (int i = 0; i < activatedNodes.length; i++) {
	    Project project = activatedNodes[i].getLookup().lookup(Project.class);
	    if( project != null ) {
                CsmProject csmProject = CsmModelAccessor.getModel().getProject(project);
		if (csmProject != null) {
		    analyzer.analyzeProject(csmProject);
		}
	    }
	}
	Collection<Issue> issues = analyzer.getIssues();
        if (!issues.isEmpty()) {
            new IssuesDisplayer().display(issues);
        }
    }    

We are done!

Try this out

Let's try this out:

    • Launch your project (press F6 in the NetBeans IDE).
    • Create a simple application. Add a new C++ file and write a class with a destructor that isn't virtual.
    • Select the project in Projects window. Select Tools > Analyze Code. The warning message appears in Output Window.
    • Control-click the warning. The code will be opened and the caret will be located at destructor.

Project Features

About this Project

CND was started in November 2009, is owned by DimaZh, and has 131 members.
By use of this website, you agree to the NetBeans Policies and Terms of Use (revision 20140418.2d69abc). © 2013, Oracle Corporation and/or its affiliates. Sponsored by Oracle logo
 
 
Close
loading
Please Confirm
Close