Integrating with Static Analysis Tools

A common question I hear is: "How can I integrate Code Collaborator with a static analysis tool?" The answer lies in the command line tool that is part of Code Collaborator: ccollab. It provides a wide array of features, including two that are key for this type of integration:

  • extract detailed information about a code review
  • add a comment (or defect) to a code review

To demonstrate, I created a little contrived example in Java:


 1 package process.sub;
2
3 public class Two {
4   public static void methodTwo( int n )
5   {
6    int contrived = 0;
7     contrived = contrived = 5;
8     contrived = contrived++;
9     System.out.println(contrived);
10   }
11 }
12

FindBugs will report some problems with this code. Among other things, it will complain about the double assignment of a field on line 7.

To pull results from another tool into a Code Collaborator review, it helps if that tool can produce output that is easy to parse. With FindBugs, that is no problem: I used its -xml:withMessages flag. And it produced an XML file that contained (among other entries):



<BugInstance type="SA_LOCAL_DOUBLE_ASSIGNMENT" priority="2" abbrev="SA"
category="CORRECTNESS" instanceHash="21c3ddb607c3eff5f35d3f55b87f0454"
instanceOccurrenceNum="0" instanceOccurrenceMax="0">
<ShortMessage>Double assignment of local variable </ShortMessage>
<LongMessage>Double assignment of contrived in process.sub.Two.methodTwo(int)</LongMessage>
<Class classname="process.sub.Two" primary="true">
<SourceLine classname="process.sub.Two" start="3" end="10"
sourcefile="Two.java" sourcepath="process/sub/Two.java">
<Message>At Two.java:[lines 3-10]</Message>
</SourceLine>
<Message>In class process.sub.Two</Message>
</Class>
<Method classname="process.sub.Two" name="methodTwo"
signature="(I)V" isStatic="true" primary="true">
<SourceLine classname="process.sub.Two" start="6" end="10"
startBytecode="0" endBytecode="86" sourcefile="Two.java"
sourcepath="process/sub/Two.java"/>
<Message>In method process.sub.Two.methodTwo(int)</Message>
</Method>
<LocalVariable name="notUsed" register="1" pc="5" role="LOCAL_VARIABLE_NAMED">
<Message>Local variable named contrived</Message>
</LocalVariable>
<SourceLine classname="process.sub.Two" primary="true" start="7" end="7"
startBytecode="5" endBytecode="5" sourcefile="Two.java"
sourcepath="process/sub/Two.java">
<Message>At Two.java:[line 7]</Message>
</SourceLine>
</BugInstance>

To make this usable by Code Collaborator I wrote a little script that takes two parameters:

  • a review number
  • the XML output from FindBugs

After running my script, my review had comments added to it:


DblA


The script could be extended to also run FindBugs instead of using existing FindBugs output, but FindBugs works best when it is run across an entire project. Since a code review will typically not include all files in a project, it felt to me that FindBugs should be run separately.


Another environment-dependent issue has to do with file paths. Depending upon how they are invoked, FindBugs and Code Collaborator might not use the exact same path to describe the location of a source file. So in the script I sort of punt on that issue and use the base file name only, without the path. An environment-dependent fix could be put in place for that.


While this is written in Groovy for use with FindBugs, the concepts in it could be applied in any scripting language to any static analysis tool that produces parsable output:



// ParseFB.groovy
//
// Given a review number and the XML output from FindBugs, for each
// Java source file in the review, will retrieve any
// FindBugs bugs reported in the XML
// and add a comment to the review for each.
//
// NOTE: the FindBugs XML must be created with the -xml:withMessages option

// if not enough args, cannot do anything
if (args.length < 2) {
println "Usage: ParseFB <reviewId> <xml file from FindBugs>"
return
}
reviewId = args[0]
fbXMLFile = args[1]

// open up the FindBugs output so that we can just exit now if there is an error
try {
fbXML = new XmlSlurper().parse(new File(fbXMLFile))
}
catch( ex ) {
println "Unable to open: $fbXMLFile - ${ex.class.name} : ${ex.message}"
return
}

// the Code Collaborator client needs some global values
ccCmd = "ccollab"
ccURL = "http://localhost:6803"
ccUser = "findbugs"
ccPw = """"

// and the values have to be formatted
ccOptions = "--url $ccURL --user $ccUser --password $ccPw --quiet --non-interactive"

// retrieve the review XML from Code Collaborator,
// using XPath to limit the output to just file names
command = """ $ccCmd $ccOptions admin review-xml $reviewId
--xpath //reviews/review/artifacts/artifact/path """
outStream = new StringBuffer()
errStream = new StringBuffer()
proc = command.execute()
proc.consumeProcessOutput(outStream, errStream) // Groovy way of preventing waitFor() hang
proc.waitFor()

// exit on error from Code Collaborator
if (proc.exitValue() != 0) {
println "Error code from ccollab command: ${ proc.exitValue()}"
println "Error: $errStream"
if (outStream.length() > 0)
println "Standard output: $outStream"
return
}

// bust up the output string that has all the file names into individual entries
filesInReview = outStream.toString().split("</path>")

// only want .java files from the review, since that's all FindBugs processes
filesInReview.each {
if (it.toLowerCase().endsWith(".java"))
putInComments(it)
}


// and we're done!
return

// for a .java file, locate any FindBugs entries
// and insert each into the review
def putInComments (file) {

// there are potential path issues when comparing the name as
// understood by Code Collaborator with the name as understood by
// FindBugs. Code Collaborator records the path based on how the
// code review was created, which is typically relative to a version
// control system file path. For example, Code Collaborator might record a file
// path from a review created in a Subversion environment as:
//
// /Hello/process/sub/Two.java
//
// and FindBugs would refer to that same file's path relative to
// the *build* environment as:
//
// process/sub/Two.java
//
// So bust up the name that Code Collaborator uses in order
// to get the filename *without* a path. Note that using this
// approach introduces a potential bug: if there are two classes
// with the same class name but they are in different packages then
// looking up either class in the FindBugs XML could return
// the FindBugs data for the wrong class. This problem can be resolved
// by adding some code here to parse/lookup based on the full file path,
// but that code would have to be specific to a project's environment.
components = file.split("/")
fbFileName = components[components.length-1]

// the Code Collaborator Command line will need the full file name,
// but without the XML tag
components = file.split("<path>")
ccFileName = components[components.length-1]

// for each FindBugs entry for a file, put in a comment
entries = fbXML.BugInstance.findAll {
it.Class.SourceLine.@sourcefile.toString() == fbFileName
}
entries.each {
lineNumber = it.SourceLine.@start.toString()
commentText = it.ShortMessage.toString()
command = """ $ccCmd $ccOptions admin review comment create --file
$ccFileName --line-number $lineNumber $reviewId

"$commentText" """
println "Processing: $command"

outStream = new StringBuffer()
errStream = new StringBuffer()
proc = command.execute()
proc.consumeProcessOutput(outStream, errStream) // Groovy way of preventing waitFor() hang
proc.waitFor()

// exit on error from Code Collaborator
if (proc.exitValue() != 0) {
println "Error code from ccollab command: ${ proc.exitValue()}"
println "Error: $errStream"
if (outStream.length() > 0)
println "Standard output: $outStream"
return
}
}
}



Close

Add a little SmartBear to your life

Stay on top of your Software game with the latest developer tips, best practices and news, delivered straight to your inbox

By submitting this form, you agree to our
Terms of Use and Privacy Policy

Thanks for Subscribing

Keep an eye on your inbox for more great content.

Continue Reading