Difference between revisions of "Discovering Valid Java Main Methods"
BenHolland (Talk | contribs) (→Step 3) Create Analyzer) |
BenHolland (Talk | contribs) |
||
Line 32: | Line 32: | ||
@Override | @Override | ||
public String getDescription() { | public String getDescription() { | ||
− | return " | + | return "Locates valid Java main methods."; |
} | } | ||
Line 38: | Line 38: | ||
public String[] getAssumptions() { | public String[] getAssumptions() { | ||
return new String[]{"Main methods are methods.", | return new String[]{"Main methods are methods.", | ||
− | + | "Main methods are case-sensitively named \"main\"", | |
− | + | "Main methods are public.", | |
− | + | "Main methods are static.", | |
− | + | "Main methods return void.", | |
− | + | "Main methods take a single String array parameter", | |
− | + | "Main methods may be final.", | |
− | + | "Main methods may have the strictfp keyword.", | |
− | + | "Main methods may be synchronized."}; | |
} | } | ||
Line 86: | Line 86: | ||
@Override | @Override | ||
public String getDescription() { | public String getDescription() { | ||
− | return " | + | return "Locates valid Java main methods."; |
} | } | ||
Line 92: | Line 92: | ||
public String[] getAssumptions() { | public String[] getAssumptions() { | ||
return new String[]{"Main methods are methods.", | return new String[]{"Main methods are methods.", | ||
− | + | "Main methods are case-sensitively named \"main\"", | |
− | + | "Main methods are public.", | |
− | + | "Main methods are static.", | |
− | + | "Main methods return void.", | |
− | + | "Main methods take a single String array parameter", | |
− | + | "Main methods may be final.", | |
− | + | "Main methods may have the strictfp keyword.", | |
− | + | "Main methods may be synchronized."}; | |
} | } | ||
Revision as of 19:10, 5 February 2015
The Toolbox Commons project defines an Analyzer
interface that encapsulates the logic for traversing a program graph to extract an "envelope" (a subgraph that is either empty if a property is satisfied or non-empty containing the necessary information to locate the violation of the property). Analyzers encapsulate their descriptions, assumptions, analysis context, and analysis logic. Of course you can define your own "Analyzer" simply by writing a program with your analysis logic, but we find this abstraction helps keep code organized when contributing to a toolbox project.
Contents
Development Process
Let's start off with a simple analysis goal. Write an analyzer that discovers all valid Java main methods in a program. We might want to discover main methods to locate developer test code or alternate entry points into an application.
Step 1) Understanding the problem
The first step is always to ask yourself if you really understand the problem. Now is the time to do some background research. Can main methods be located in inner classes? Can main methods be final? Can main methods return anything other than void? This blog has enumerated through several variations on main methods.
Step 2) Developing test cases
A little upfront work to create a decent test set will likely save you a lot of time in the development of your analyzer since you will be able to quickly identify the cases you are not handling correctly. For this tutorial we've already created an application with several test cases for you! Just checkout the https://github.com/EnSoftCorp/LearningAtlas repository and import the MainMethodTestCases
Eclipse project into your workspace. Since you didn't have to go through the work of developing the test cases yourself, its probably a good idea to go through the test cases now. We have created several Java classes, each with a main method. The classes with valid main methods are in the com.example.valid
package, whereas the classes with invalid main methods are in the com.example.invalid
package.
Step 3) Create Analyzer
From the exercise of going through steps 1 and 2, we can now create a new Analyzer and document our assumptions (at least the assumptions we've made so far).
In the Starter Toolbox create a new class in the toolbox.analysis.analyzers
package. Name the class DiscoverMainMethods
and extend the com.ensoftcorp.open.toolbox.commons.analysis.Analyzer
base class.
Let's take this opportunity to fill out the information that we know so far. We learned that Java main methods must be public, static, void methods named "main" (case-sensitive). Main methods take a single parameter of a one-dimensional String array. The main method may optionally be marked as final, synchronized, or strictfp. After documenting this information, your analyzer should look something like the following.
package toolbox.analysis.analyzers; import com.ensoftcorp.atlas.core.query.Q; import com.ensoftcorp.open.toolbox.commons.analysis.Analyzer; public class DiscoverMainMethods extends Analyzer { @Override public String getName(){ return "Discover Main Methods"; } @Override public String getDescription() { return "Locates valid Java main methods."; } @Override public String[] getAssumptions() { return new String[]{"Main methods are methods.", "Main methods are case-sensitively named \"main\"", "Main methods are public.", "Main methods are static.", "Main methods return void.", "Main methods take a single String array parameter", "Main methods may be final.", "Main methods may have the strictfp keyword.", "Main methods may be synchronized."}; } @Override protected Q evaluateEnvelope() { // TODO: Implement return null; } }
Step 4) Develop and Debug Analyzer Logic
TODO
Analysis Step 1) Select public static methods
TODO
Analysis Step 2) Select methods named "main"
TODO
Analysis Step 3) Select methods that return void
TODO
Analysis Step 4) Select methods that only take one parameter
TODO
Analysis Step 5) Select methods that take a String array
TODO
Final Implementation
public class DiscoverMainMethods extends Analyzer { @Override public String getName(){ return "Discover Main Methods"; } @Override public String getDescription() { return "Locates valid Java main methods."; } @Override public String[] getAssumptions() { return new String[]{"Main methods are methods.", "Main methods are case-sensitively named \"main\"", "Main methods are public.", "Main methods are static.", "Main methods return void.", "Main methods take a single String array parameter", "Main methods may be final.", "Main methods may have the strictfp keyword.", "Main methods may be synchronized."}; } @Override protected Q evaluateEnvelope() { // Step 1) select nodes from the index that are marked as public, static, methods Q mainMethods = appContext.nodesTaggedWithAll(Node.IS_PUBLIC, Node.IS_STATIC, Node.METHOD); // Step 2) select nodes from the public static methods that are named "main" mainMethods = mainMethods.selectNode(Node.NAME, "main"); // Step 3) filter out methods that are not void return types mainMethods = mainMethods.intersection(Common.stepFrom(Common.edges(Edge.RETURNS), Common.types("void"))); // Step 4) filter out methods that do not take exactly one parameter Q paramEdgesInContext = appContext.edgesTaggedWithAny(Edge.PARAM).retainEdges(); // methods with no parameters will not have a PARAM edge Q methodsWithNoParams = mainMethods.difference(Common.stepFrom(paramEdgesInContext, Common.stepTo(paramEdgesInContext, mainMethods))); // methods with 2 or more params will have at least one edge with PARAMETER_INDEX == 1 (index 0 is the first parameter) Q methodsWithTwoOrMoreParams = Common.stepFrom(paramEdgesInContext, Common.stepTo(paramEdgesInContext, mainMethods).selectNode(Node.PARAMETER_INDEX, 1)); mainMethods = mainMethods.difference(methodsWithNoParams, methodsWithTwoOrMoreParams); // Step 5) filter out methods that do not take a String array // get the 1-dimensional String array type Q stringArrays = Common.stepFrom(Common.edges(Edge.ELEMENTTYPE), Common.typeSelect("java.lang","String")); Q oneDimensionStringArray = stringArrays.selectNode(Node.DIMENSION, 1); Q mainMethodParams = CommonQueries.methodParameter(mainMethods, 0); Q validMethodParams = mainMethodParams.intersection(Common.stepFrom(Common.edges(Edge.TYPEOF), oneDimensionStringArray)); mainMethods = Common.stepFrom(paramEdgesInContext, validMethodParams); return mainMethods; } }
Alternative Implementation
TODO