Purifier1 Special Cases

Exception Handler Object Conflict Issue

Brief Description

This special case is an issue with Exception handlers dealing with a particular conflict case differently than everywhere else. Most Exception handlers start with an astore instruction that puts the Exception class onto the LocalVariables array over top of an existing type. Normally, a circumstance like that would be a conflict, but Sun's preverifier seems to resolve it by setting the type to a java.lang.Object type rather than null.

Status

A solution is in place for this issue, but at least one case has been found where it makes an otherwise passing test case fail.

Action Items

  1. More investigation to determine if the current theory is correct.
  2. Also need to investigate how this works in try-catch blocks nested inside of catch blocks.

Test Cases Exhibiting Issue

Test Cases Exhibiting Exception Handler Object Conflict Issue
Package Class Method Method # Effect Source
com.markcrocker.purifier.testcases ProblemCases exceptionHandlerObjectConflict() 6 Would Fail without fix Purifier1 project
nullObjectNeverInit() 7 Won't fail unless fix is faulty
nestedExceptionHandlersConflict(Integer[]) 11 Fails partly because of fix!
UniqueProblemCases nestedExceptionHandlersConflictWithUnknown(Integer[]) 6
catchesNestedExceptions(Integer[]) 7
ExceptionCases 13
examples.stock StockMidlet viewAlerts() 14 Wireless Toolkit 1.04
checkAlerts(String) 8
calc() 4
destroyApp(boolean) 3
StockMidlet$StockRefreshTask run() 1
example.http HttpTest readHeaders(String) 7

Details

This case involves an Exception handler that starts with an astore instruction that puts the Exception class onto the LocalVariables array in the same slot as an existing type from another, competing branch source. Normally, when the Frame is pushed onto the BranchStack the LocalVariables.resolveConflicts method detects a conflict between the two types and sets the slot to null. The resolveConflicts method does this because it assumes that when two different Frames from different branch sources that lead to the same branch target have a LocalVariables slot that holds incompatible types, then the types represent junk data that can be discarded. However, Sun's preverifier seems to resolve this particular case by setting the type to a java.lang.Object type rather than null. It only does this if both LocalVariables slots hold ITEM_Object entries. If either is of any other type, then it reverts to the standard behaviour, which is to set the entry to null.

Example

ProblemCases.exceptionHandlerObjectConflict provides an example of this issue and is shown below.

Source Code

1:  public void exceptionHandlerObjectConflict() {
2:    try {
3:      Integer[] eye = new Integer[5];
4:      eye[9] = new Integer(13);
5:    } catch (ArrayIndexOutOfBoundsException aioob ) {
6:    }
7:  }

Bytecode

The following byte code is from the compiled and preverified class file created from the source in the previous section. Unrelated attributes have been removed for brevity and the StackMap reformatted for readability.

0:    iconst_5
1:    anewarray		<java.lang.Integer> (14)
4:    astore_1
5:    aload_1
6:    bipush		9
8:    new		<java.lang.Integer> (14)
11:   dup
12:   bipush		13
14:   invokespecial	java.lang.Integer.<init> (I)V (15)
17:   aastore
18:   goto		#22
21:   astore_1
22:   return

Exception handler(s) =
From	To	Handler	Type
0	18	21	java.lang.ArrayIndexOutOfBoundsException(13)

Data Flow Analysis

At offset 22, there is a conflict between the ArrayIndexOutOfBoundsException which is put into LocalVariables slot 1 by the astore_1 instruction at offset 21 and the Integer array that is stored in the same slot by the astore_1 instruction at offset 4.

These Frames conflict because the branch source from the goto instruction at offset 18 and the fall through from the instruction at offset 21 both lead to the return instruction at offset 22. The first branch source has an Integer array pushed onto the stack by the anewarray instruction at offset 1 and stored on the local variables at offset 4. At the goto branch instruction at offset 18, the Frame is:

Local Variables:
	0: (type=Object, class=com.markcrocker.purifier.testcases.ProblemCases)
	1: (type=Object, class=[Ljava.lang.Integer;)
OperandStack:
	Slots used: 0 MaxStack: 5.

The second branch source is an exception handler, which starts at offset 21 and is automatically initialized with the ArrayIndexOutOfBoundsException on the stack. This type is then stored in the same slot as the Integer array from the last branch by the astore_1 instruction at offset 21, which falls through to instruction 22. Resulting in a Frame of:

Local Variables:
	0: (type=Object, class=com.markcrocker.purifier.testcases.ProblemCases)
	1: (type=Object, class=java.lang.ArrayIndexOutOfBoundsException)
OperandStack:
	Slots used: 0 MaxStack: 5.

Normally, the LocalVariables.resolveConflicts method, which is invoked at the return instruction at offset 22, would see the [Ljava.lang.Integer; and java.lang.ArrayIndexOutOfBoundsException in LocalVariables slot 1 as incompatible and would set the slot to null. In this case, however, the slot is set to java.lang.Object. The resulting StackMap is:

StackMap(
	(
		offset = 21,
		locals = {
			(type=Object, (20)class=com.markcrocker.purifier.testcases.ProblemCases)
		},
		stack items = {
			(type=Object, (13)class=java.lang.ArrayIndexOutOfBoundsException)
		}
	)
	(
		offset = 22,
		locals = {
			(type=Object, (20)class=com.markcrocker.purifier.testcases.ProblemCases),
			(type=Object, (104)class=java.lang.Object)
		}
	)
)

Note that despite the locals at offset 22 have a java.lang.Object in the second record.

More Examples

The ProblemCases.exceptionHandlerPrimitiveConflict test case tests an opposing situation where the conflict really should be resolved by setting the LocalVariables slot to null. This helps ensure that the solutions to this problem don't remove ITEM_Nulls when they actually do belong.

One case that shows where the solution breaks down is UniqueProblemCases.nestedExceptionHandlersConflictWithUnknown(Integer[]), where the expected StackMap entry at offset 24 is:

offset = 24,
locals = {
	(type=Object, (17)class=com.markcrocker.purifier.testcases.UniqueProblemCases),
	(type=Object, (85)class=[Ljava.lang.Integer;),
	(type=Integer),
	(type=Object, (15)class=java.lang.Integer),
	(type=Object, (86)class=java.lang.Object)
}

However, the Purifier1 produces:

offset = 24,
locals = {
	(type=Object, (17)class=com.markcrocker.purifier.testcases.UniqueProblemCases),
	(type=Object, (85)class=[Ljava.lang.Integer;),
	(type=Integer),
	(type=Object, (86)class=java.lang.Object),
	(type=Object, (12)class=java.lang.NullPointerException)
}

This shows that sometimes the Purifier1 misses cases that it should catch and also catches some that should be left alone.

Solutions

A LocalVariables.setAstoreOverride method and supporting methods and fields have been added so that the LocalVariables has a state indicating that a particular LocalVariables slot should resolve any conflicts between ITEM_Object types as java.lang.Object instead of null. Some detection code has been added to the Data FLow Analysis code of the StackMapGen.generateStackMap method to use the setAstoreOverride of the current Frame whenever an astore instruction in first instruction of an Exception handler is found. Unfortunately, a case has been found where this actually causes an otherwise passing test case to fail, so this solution is not adequate.

Back to Developer's Guide   Purifier1 Home Page

Valid HTML 4.01!