In this article we will understand another advanced variant of static code analysis which is byte code analysis. SpotBugs (Former FindBugs) does this kind of analysis using Apache commons-bcel library.
In earlier article we went through static code analysis using lexical analysis. Here is the link for the same to go through basics.
Do your own static code analysis programmatically in Java | Similar to checkstyle, PMD
What is Byte Code Analysis?
- Java Byte Code
- When Java source code is compiled, then source is converted into byte code which is nothing but set of certain instructions to JVM.
- When JVM runs program, then it simply runs instructions from the byte code.
- Op Code
- A Java Virtual Machine “instructions” consists of an opcode specifying the operation to be performed.
- Here is detailed documentations of op codes from Oracle – The Java Virtual Machine Instruction Set
- Byte Code manipulation / analysis / engineering
- Byte code manipulation is a process of accessing byte code of classes, analyzing it, modifying it etc.
- There are libraries available like Apache commons-bcel, ASM which provides easy ways to go through class bytecode, methods, opcodes etc.
- We can use such libraries, go through byte code & perform logic to find out possible issues or defects with the code & return them as result of static code analysis.
Example in this article
We will take example similar to what findbugs do in their code which might help relate to actual scenarios & to easily understand code of findbugs in case you wish to contribute.
- In this article we will create a very simple static code analysis example using Apache commons-bcel library.
- We will check if the java source code has an empty synchronized block.
- If empty synchronized block is found, we will provide output with the line number & error message where empty synchronized block is found. Else will will output that class is all good.
Let start coding
For this code, we will need maven/gradle dependency which can be found here in maven repository.
Lets first create sample source code to analyze. We will create one bad class which will have empty synchronized block & another good class which has non-empty synchronized block as given below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package com.itsallbinary.staticanalysis; public class AnalyzeMeGoodCode { public static void main(String[] args) { System.out.println("This is a sample class to analyze. This code has no issues."); // NON Empty synchronized block. Should NOT be marked as problem in static // analysis. synchronized (AnalyzeMeGoodCode.class) { System.out.println("Test synchronized block"); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package com.itsallbinary.staticanalysis; public class AnalyzeMeBadCode { public static void main(String[] args) { System.out .println("This is a sample class to analyze. This code has an issue due to empty synchronized block."); // Empty synchronized block. Should be marked as problem in static analysis. synchronized (AnalyzeMeBadCode.class) { } System.out.println(); } } |
Now lets look at actual code to perform static code analysis. Code level comments are added to provide further understanding of code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
package com.itsallbinary.staticanalysis; import java.io.IOException; import org.apache.bcel.Const; import org.apache.bcel.Repository; import org.apache.bcel.classfile.ClassFormatException; import org.apache.bcel.classfile.JavaClass; import org.apache.bcel.classfile.Method; import org.apache.bcel.util.ByteSequence; public class BCELExamples { public static void main(String[] args) throws ClassFormatException, IOException, ClassNotFoundException { // Classes to analyze. Class<?>[] classesToAnalyze = new Class[] { AnalyzeMeGoodCode.class, AnalyzeMeBadCode.class }; for (Class<?> classToAnalyze : classesToAnalyze) { // Lookup class which needs to be statically analyzed. JavaClass javaClass = Repository.lookupClass(classToAnalyze); boolean hasEmptySyncBlock = false; int lineNumberOfEmptySyncBlock = -1; // Iterate through all the methods of this class. for (Method method : javaClass.getMethods()) { // Get byte code of source code for this method byte[] code = method.getCode().getCode(); try (ByteSequence stream = new ByteSequence(code)) { short previousOpCode = -1; // Iterate through bytes stream till bytes are available. for (int i = 0; stream.available() > 0; i++) { // Get line number int lineNumber = stream.getIndex(); // Find out JVM instruction opcode for this line of code. short opCode = (short) stream.readUnsignedByte(); // Uncomment below line to see all op codes. // System.out.println(lineNumber + " = " + Const.getOpcodeName(opCode)); /* * If previous line of code was start of synchronized block & this line of code * is end of synchronized block, that means there is an empty synchronized * block. Mark this as problem. */ if (previousOpCode != -1 && Const.MONITORENTER == previousOpCode && Const.MONITOREXIT == opCode) { hasEmptySyncBlock = true; lineNumberOfEmptySyncBlock = lineNumber; } previousOpCode = opCode; } } catch (final IOException e) { e.printStackTrace(); } } // Output the result of analysis. if (hasEmptySyncBlock) { System.out.println("##### PROBLEM: \n\tClass = " + classToAnalyze.getName() + " | Line Number = " + lineNumberOfEmptySyncBlock + "\n\tError = Empty synchronized block found. Please verify & remove if not needed."); } else { System.out.println("##### ALL GOOD. Class = " + classToAnalyze.getName()); } } } } |
Here is the output of analysis. As you can see our program correctly identified class with empty synchronized block & provided line number & error message.
1 2 3 4 |
##### ALL GOOD. Class = com.itsallbinary.staticanalysis.AnalyzeMeGoodCode ##### PROBLEM: Class = com.itsallbinary.staticanalysis.AnalyzeMeBadCode | Line Number = 12 Error = Empty synchronized block found. Please verify & remove if not needed. |
Complete source code can be found in GitHub repository here.