Difference between revisions of "Discovering Valid Java Main Methods"

From AtlasWiki
Jump to: navigation, search
Line 1: Line 1:
 
The [https://ensoftcorp.github.io/toolbox-commons/ Toolbox Commons] project defines an <code>Analyzer</code> 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.
 
The [https://ensoftcorp.github.io/toolbox-commons/ Toolbox Commons] project defines an <code>Analyzer</code> 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.
 +
 +
Let's start off with a simple analysis goal.  Write an analyzer that discovers all valid [http://docs.oracle.com/javase/tutorial/getStarted/application/#MAIN 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 [https://rationalpi.wordpress.com/2007/01/29/main-methods-in-java/ blog] has enumerated through several variations on main methods. 
 +
 +
== Step 2) Develop 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 <code>MainMethodTestCases</code> Eclipse project into your workspace.
  
 
<pre>
 
<pre>
 
public class DiscoverMainMethods extends Analyzer {
 
public class DiscoverMainMethods extends Analyzer {
 
+
 
@Override  
 
@Override  
 
public String getName(){
 
public String getName(){
Line 16: Line 24:
 
@Override
 
@Override
 
public String[] getAssumptions() {
 
public String[] getAssumptions() {
return new String[]{"All main methods are named \"main\".",  
+
return new String[]{"Main methods are methods.",
"All main methods are public.",
+
"Main methods are case-sensitively named \"main\"",
"All main methods are static.",
+
"Main methods are public.",  
"All main methods return void.",
+
"Main methods are static.",  
"All main methods take a single parameter of a String array."};
+
"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 26: Line 38:
 
protected Q evaluateEnvelope() {
 
protected Q evaluateEnvelope() {
 
// Step 1) select nodes from the index that are marked as public, static, methods
 
// Step 1) select nodes from the index that are marked as public, static, methods
Q mainMethods = context.nodesTaggedWithAll(Node.IS_PUBLIC, Node.IS_STATIC, Node.METHOD);
+
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"
 
// Step 2) select nodes from the public static methods that are named "main"
Line 35: Line 47:
 
 
 
// Step 4) filter out methods that do not take exactly one parameter
 
// Step 4) filter out methods that do not take exactly one parameter
Q paramEdgesInContext = context.edgesTaggedWithAny(Edge.PARAM).retainEdges();
+
Q paramEdgesInContext = appContext.edgesTaggedWithAny(Edge.PARAM).retainEdges();
// methods with no parameteres will not have a PARAM edge
+
// methods with no parameters will not have a PARAM edge
 
Q methodsWithNoParams = mainMethods.difference(Common.stepFrom(paramEdgesInContext, Common.stepTo(paramEdgesInContext, mainMethods)));
 
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)
 
// methods with 2 or more params will have at least one edge with PARAMETER_INDEX == 1 (index 0 is the first parameter)

Revision as of 18: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.

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) Develop 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 [1] repository and import the MainMethodTestCases Eclipse project into your workspace.

public class DiscoverMainMethods extends Analyzer {
	
	@Override 
	public String getName(){
		return "Discover Main Methods";
	}
	
	@Override
	public String getDescription() {
		return "Finds methods named \"main\" that are public static void methods that take a String array.";
	}

	@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;
	}
	
}

Back to Learning Atlas