/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Business Objects nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* NullingBytecodeRewriterTest.java
* Created: October 15, 2007
* By: Malcolm Sharpe
*/
package org.openquark.cal.internal.javamodel;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.openquark.cal.internal.javamodel.NullingBytecodeRewriter;
import org.openquark.cal.internal.javamodel.NullingClassAdapter;
import junit.framework.TestCase;
public class NullingBytecodeRewriterTest extends TestCase {
private static String rewriterPackage = "org.openquark.cal.internal.javamodel";
private static String packagePath = "org/openquark/cal/internal/javamodel";
/**
* Test that the given class crashes when run without rewriting.
* @param className the name of the class to test.
*/
private void failureTest(String className) {
try {
Runtime rt = Runtime.getRuntime();
Process p = rt.exec("java -Xmx128m -cp test "+rewriterPackage+"."+className);
assertTrue("program completed successfully", 0 != p.waitFor());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void writeBytes(byte[] bytes, String path) throws IOException {
File f = new File(path);
File d = f.getParentFile();
if (!d.exists()) {
assertTrue("failed to create test binary directory", d.mkdirs());
}
FileOutputStream out = new FileOutputStream(path);
out.write(bytes);
out.close();
}
/**
* Test that the given class runs successfully with rewriting and produces
* the given line of output on stdout.
* @param className the name of the class to test.
* @param expectedOutput the expected output line.
*/
private void successTest(String className, String expectedOutput) {
try {
byte[] result = NullingBytecodeRewriter.rewriteClass(new FileInputStream("test/"+packagePath+"/"+className+".class"));
writeBytes(result, "testbin/"+packagePath+"/"+className+".class");
Runtime rt = Runtime.getRuntime();
Process p = rt.exec("java -Xmx128m -cp testbin "+rewriterPackage+"."+className);
InputStreamReader esr = new InputStreamReader(p.getErrorStream());
BufferedReader ebr = new BufferedReader(esr);
String line = ebr.readLine();
if (line != null) {
fail(line);
}
InputStreamReader isr = new InputStreamReader(p.getInputStream());
BufferedReader br = new BufferedReader(isr);
assertEquals(expectedOutput, br.readLine());
assertEquals(-1, br.read());
// Ensure that the program completes successfully.
assertEquals(0, p.waitFor());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void assertBytesEqual(byte[] expected, byte[] actual) {
assertEquals("lengths differ;", expected.length, actual.length);
for (int i = 0; i < expected.length; i++) {
assertEquals("difference at offset "+i+";", expected[i], actual[i]);
}
}
/**
* Test that the given class, when rewritten, is a fixed point for the
* rewriter.
* @param className the name of the class to test.
*/
private void fixedPointTest(String className) {
try {
byte[] result1 = NullingBytecodeRewriter.rewriteClass(new FileInputStream("test/"+packagePath+"/"+className+".class"));
ByteArrayInputStream bais = new ByteArrayInputStream(result1);
byte[] result2 = NullingBytecodeRewriter.rewriteClass(bais);
assertBytesEqual(result1, result2);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Test that the given class, when rewritten without nulling this, is not any different.
* @param className the name of the class to test.
*/
private void unchangedTest(String className) {
try {
String path = "test/"+packagePath+"/"+className+".class";
byte[] original = new byte[(int)new File(path).length()];
InputStream is = new FileInputStream(path);
assertEquals(original.length, is.read(original));
is.close();
NullingClassAdapter.nullThis = false;
byte[] result = NullingBytecodeRewriter.rewriteClass(new FileInputStream(path));
NullingClassAdapter.nullThis = true;
writeBytes(result, "testbin/"+packagePath+"/"+className+".class");
// For now, do not compare the individual bytes, since the ASM library
// reorders various things in the class file without changing the meaning
// or size of the file.
assertEquals("lengths differ;", original.length, result.length);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void testNoChange() {
// Test that, when nulling is not required, no change is made.
successTest("NoChange", "Hello World");
}
// A simple case with only local array references holding space.
public void testLocalsOnly() {
failureTest("LocalsOnly");
}
public void testLocalsOnlyRewritten() {
successTest("LocalsOnly", Integer.valueOf(SIZE*2).toString());
}
public void testLocalsOnlyFixedPoint() {
fixedPointTest("LocalsOnly");
}
// Object references.
public void testObjectReferences() {
failureTest("ObjectReferences");
}
public void testObjectReferencesRewritten() {
successTest("ObjectReferences", Integer.valueOf(SIZE*2).toString());
}
public void testObjectReferencesFixedPoint() {
fixedPointTest("ObjectReferences");
}
// A primitive argument in use.
public void testPrimitiveArg() {
failureTest("PrimitiveArg");
}
public void testPrimitiveArgRewritten() {
successTest("PrimitiveArg", Integer.valueOf(SIZE*2+1).toString());
}
public void testPrimitiveArgFixedPoint() {
fixedPointTest("PrimitiveArg");
}
// A 2-stack-slot wide primitive argument in use.
public void testWidePrimitiveArg() {
failureTest("WidePrimitiveArg");
}
public void testWidePrimitiveArgRewritten() {
successTest("WidePrimitiveArg", Integer.valueOf(SIZE*2+1).toString());
}
public void testWidePrimitiveArgFixedPoint() {
fixedPointTest("WidePrimitiveArg");
}
// An instance method.
public void testInstanceMethod() {
failureTest("InstanceMethod");
}
public void testInstanceMethodRewritten() {
successTest("InstanceMethod", Integer.valueOf(SIZE*2).toString());
}
public void testInstanceMethodFixedPoint() {
fixedPointTest("InstanceMethod");
}
// A case where 'this' must be nulled.
public void testNullThis() {
failureTest("NullThis");
}
public void testNullThisRewritten() {
successTest("NullThis", Integer.valueOf(SIZE*2).toString());
}
public void testNullThisFixedPoint() {
fixedPointTest("NullThis");
}
// A dead assignment, which requires some special case handling in the rewriter.
public void testDeadAssignment() {
failureTest("DeadAssignment");
}
public void testDeadAssignmentRewritten() {
successTest("DeadAssignment", Integer.valueOf(SIZE+1).toString());
}
public void testDeadAssignmentFixedPoint() {
fixedPointTest("DeadAssignment");
}
// A nulling occurring at the beginning of an if branch.
public void testIfBranch() {
failureTest("IfBranch");
}
public void testIfBranchRewritten() {
successTest("IfBranch", Integer.valueOf(SIZE*2).toString());
}
public void testIfBranchFixedPoint() {
fixedPointTest("IfBranch");
}
// A nulling occurring at the beginning of an else branch.
// This is tricky since the nulling occurs at a label, but inserting
// the nulling instructions _before_ the label will not work.
public void testElseBranch() {
failureTest("ElseBranch");
}
public void testElseBranchRewritten() {
successTest("ElseBranch", Integer.valueOf(SIZE*2).toString());
}
public void testElseBranchFixedPoint() {
fixedPointTest("ElseBranch");
}
// Check that the rewriter does not break when faced with try-catch blocks.
public void testTryCatch() {
failureTest("TryCatch");
}
public void testTryCatchRewritten() {
successTest("TryCatch", Integer.valueOf(SIZE*3).toString());
}
public void testTryCatchFixedPoint() {
fixedPointTest("TryCatch");
}
// Check that the rewriter does not break when faced with try-finally blocks.
public void testTryFinally() {
failureTest("TryFinally");
}
public void testTryFinallyRewritten() {
successTest("TryFinally", Integer.valueOf(SIZE*2).toString());
}
public void testTryFinallyFixedPoint() {
fixedPointTest("TryFinally");
}
// A loop.
public void testLoop() {
failureTest("Loop");
}
public void testLoopRewritten() {
successTest("Loop", Integer.valueOf(SIZE*4).toString());
}
public void testLoopFixedPoint() {
fixedPointTest("Loop");
}
// A simple program where the nulling-out work has already been done.
public void testNullingDoneSimple() {
unchangedTest("NullingDoneSimple");
}
// A leaf method where nulling-out is not required since no allocation might occur.
public void testLeafMethod() {
unchangedTest("LeafMethod");
}
// TODO: A test where javac reuses a local slot, if there exists such a program.
// I failed to create a test case for this.
// TODO: A test where a method branches to its first instruction, such as having
// a do-while loop as the first statement in the method, with a non-argument object
// reference requiring nulling at the beginning of the loop. I created such a
// test case, but oddly it did not run out of memory when run without rewriting.
// With a 128-megabyte heap, one int array of this size fits
// in memory, but not two.
public static final int SIZE = 30000000;
}
class NoChange {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
class LocalsOnly {
public static void main(String[] args) {
int result = 0;
int[] a1 = new int[NullingBytecodeRewriterTest.SIZE];
result += a1.length;
int[] a2 = new int[NullingBytecodeRewriterTest.SIZE];
result += a2.length;
System.out.println(result);
}
}
class ObjectReferences {
public static void main(String[] args) {
helper(new int[NullingBytecodeRewriterTest.SIZE]);
}
private static void helper(Object o1) {
int result = 0;
result += ((int[])o1).length;
Object o2 = new int[NullingBytecodeRewriterTest.SIZE];
result += ((int[])o2).length;
System.out.println(result);
}
}
class PrimitiveArg {
public static void main(String[] args) {
helper(1, new int[NullingBytecodeRewriterTest.SIZE]);
}
private static void helper(int result, int[] a1) {
result += a1.length;
int[] a2 = new int[NullingBytecodeRewriterTest.SIZE];
result += a2.length;
System.out.println(result);
}
}
class WidePrimitiveArg {
public static void main(String[] args) {
helper(1.5, new int[NullingBytecodeRewriterTest.SIZE]);
}
private static void helper(double result, int[] a1) {
result += a1.length;
int[] a2 = new int[NullingBytecodeRewriterTest.SIZE];
result += a2.length;
System.out.println((int)result);
}
}
class InstanceMethod {
public static void main(String[] args) {
new InstanceMethod().helper(new int[NullingBytecodeRewriterTest.SIZE]);
}
private void helper(int[] a1) {
int result = 0;
result += a1.length;
int[] a2 = new int[NullingBytecodeRewriterTest.SIZE];
result += a2.length;
System.out.println(result);
}
}
class NullThis {
public static void main(String[] args) {
new NullThis(new int[NullingBytecodeRewriterTest.SIZE]).helper();
}
private int[] a1;
private NullThis(int[] a1) {
this.a1 = a1;
}
private void helper() {
int result = 0;
result += a1.length;
int[] a2 = new int[NullingBytecodeRewriterTest.SIZE];
result += a2.length;
System.out.println(result);
}
}
class DeadAssignment {
public static void main(String[] args) {
int result = 0;
int[] a1 = new int[NullingBytecodeRewriterTest.SIZE];
int[] a2 = new int[NullingBytecodeRewriterTest.SIZE];
result += a2.length;
a1 = new int[1];
result += a1.length;
System.out.println(result);
}
}
class IfBranch {
public static void main(String[] args) {
helper(true);
}
private static void helper(boolean cond) {
int result = 0;
int[] a1 = new int[NullingBytecodeRewriterTest.SIZE];
result += a1.length;
if (cond) {
int[] a2 = new int[NullingBytecodeRewriterTest.SIZE];
result += a2.length;
} else {
result += a1.length;
}
System.out.println(result);
}
}
class ElseBranch {
public static void main(String[] args) {
helper(false);
}
private static void helper(boolean cond) {
int result = 0;
int[] a1 = new int[NullingBytecodeRewriterTest.SIZE];
result += a1.length;
if (cond) {
result += a1.length;
} else {
int[] a2 = new int[NullingBytecodeRewriterTest.SIZE];
result += a2.length;
}
System.out.println(result);
}
}
class TryCatch {
public static void main(String[] args) {
int result = 0;
int[] a1 = null;
try {
a1 = new int[NullingBytecodeRewriterTest.SIZE];
result += a1.length;
throw new Exception("foo");
} catch (Exception e) {
result += a1.length;
}
int[] a2 = new int[NullingBytecodeRewriterTest.SIZE];
result += a2.length;
System.out.println(result);
}
}
class TryFinally {
public static void main(String[] args) {
int result = 0;
int[] a1 = null;
try {
int[] a2 = new int[NullingBytecodeRewriterTest.SIZE];
result += a2.length;
a1 = new int[NullingBytecodeRewriterTest.SIZE];
} finally {
result += a1.length;
}
System.out.println(result);
}
}
class Loop {
public static void main(String[] args) {
int result = 0;
int[] a1 = new int[NullingBytecodeRewriterTest.SIZE];
for (int i = 0; i < 2; i++) {
result += a1.length;
int[] a2 = new int[NullingBytecodeRewriterTest.SIZE];
result += a2.length;
a1 = new int[NullingBytecodeRewriterTest.SIZE];
}
System.out.println(result);
}
}
class NullingDoneSimple {
public static void main(String[] args) {
args = null;
}
}
class LeafMethod {
public static void main(String[] args) {
args = null;
int foo = leaf(null);
System.out.println(foo);
}
private static int leaf(int[] arg) {
return 2;
}
}