EvaluatorTest.java
/*
* Janino - An embedded Java[TM] compiler
*
* Copyright (c) 2001-2010 Arno Unkrig. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
* following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
* following disclaimer.
* 2. 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.
* 3. Neither the name of the copyright holder 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 HOLDER 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.
*/
// SUPPRESS CHECKSTYLE JavadocMethod:9999
package org.codehaus.commons.compiler.tests;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.Properties;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.codehaus.commons.compiler.CompileException;
import org.codehaus.commons.compiler.ErrorHandler;
import org.codehaus.commons.compiler.IClassBodyEvaluator;
import org.codehaus.commons.compiler.ICompilerFactory;
import org.codehaus.commons.compiler.IExpressionEvaluator;
import org.codehaus.commons.compiler.IScriptEvaluator;
import org.codehaus.commons.compiler.ISimpleCompiler;
import org.codehaus.commons.compiler.Location;
import org.codehaus.commons.compiler.util.resource.MapResourceFinder;
import org.codehaus.commons.nullanalysis.Nullable;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import util.CommonsCompilerTestSuite;
import util.TestUtil;
/**
* Tests for JANINO's {@link ExpressionEvaluator}, {@link ScriptEvaluator}, {@link ClassBodyEvaluator} and {@link
* SimpleCompiler}.
*/
@RunWith(Parameterized.class) public
class EvaluatorTest extends CommonsCompilerTestSuite {
@Parameters(name = "CompilerFactory={0}") public static Collection<Object[]>
compilerFactories() throws Exception { return TestUtil.getCompilerFactoriesForParameters(); }
public
EvaluatorTest(ICompilerFactory compilerFactory) { super(compilerFactory); }
@SuppressWarnings({ "unused", "static-method" })
@Before
public void
setUp() throws Exception {
// Enable this code snippet to print class file disassemblies to the console.
if (false) {
Logger scl = Logger.getLogger("org.codehaus.janino.SimpleCompiler");
for (Handler h : scl.getHandlers()) {
h.setLevel(Level.FINEST);
}
scl.setLevel(Level.FINEST);
}
}
@Test public void
testMultiScriptEvaluator() throws Exception {
IScriptEvaluator se = this.compilerFactory.newScriptEvaluator();
se.setOverrideMethod(new boolean[] { false, false });
se.setReturnTypes(new Class[] { double.class, double.class });
se.setMethodNames(new String[] { "funct2", "funct3" });
se.setParameters(new String[][] { { "a", "b" }, {} }, new Class<?>[][] { { double.class, double.class }, {} });
se.setStaticMethod(new boolean[] { true, true });
se.cook(new String[] { "return a + b;", "return 0;" });
Assert.assertEquals(
new Double(7.0),
se.getMethod(0).invoke(null, new Object[] { new Double(3.0), new Double(4.0) })
);
Assert.assertEquals(new Double(0.0), se.getMethod(1).invoke(null, new Object[0]));
}
@Test public void
testExpressionEvaluator() throws Exception {
IExpressionEvaluator ee = this.compilerFactory.newExpressionEvaluator();
ee.setClassName("Foo");
ee.setDefaultImports(new String[] { "java.io.*", "for_sandbox_tests.*", });
ee.setOverrideMethod(new boolean[] {
false,
false,
true,
});
ee.setStaticMethod(new boolean[] {
false,
true,
false,
});
ee.setExpressionTypes(new Class[] {
Object.class,
InputStream.class,
void.class,
});
ee.setExtendedClass(Properties.class);
ee.setImplementedInterfaces(new Class[] { Runnable.class, });
ee.setMethodNames(new String[] {
"a",
"b",
"run",
});
ee.setParameters(new String[][] {
{ "a", "b" },
{},
{},
}, new Class[][] {
{ int.class, int.class },
{},
{},
});
// ee.setParentClassLoader(BOOT_CLASS_LOADER, new Class[] { for_sandbox_tests.ExternalClass.class });
ee.setThrownExceptions(new Class[][] {
{},
{ IOException.class },
{},
});
ee.cook(new String[] {
"a + b",
"new FileInputStream(\"xyz\")",
"ExternalClass.m1()",
});
{
Method m = ee.getMethod(0);
Object instance = m.getDeclaringClass().newInstance();
Assert.assertEquals(5, m.invoke(instance, new Object[] { 2, 3 }));
}
try {
ee.evaluate(1, new Object[0]);
Assert.fail("Should have thrown an InvocationTargetException");
} catch (InvocationTargetException ex) {
Assert.assertTrue("FileNotFoundException", ex.getTargetException() instanceof FileNotFoundException);
}
try {
Method m = ee.getMethod(2);
Object instance = m.getDeclaringClass().newInstance();
m.invoke(instance, new Object[0]);
} catch (Exception e) {
Assert.fail(e.getMessage());
}
}
@Test public void
testFastClassBodyEvaluator1() throws Exception {
IClassBodyEvaluator cbe = this.compilerFactory.newClassBodyEvaluator();
cbe.setImplementedInterfaces(new Class[] { Runnable.class });
cbe.cook(
""
+ "import java.util.*;\n"
+ "@Override public void run() {\n"
+ " new ArrayList();\n"
+ " new other_package.Foo(7);\n"
+ "}\n"
);
((Runnable) cbe.getClazz().newInstance()).run();
}
@Test public void
testFastClassBodyEvaluator2() throws Exception {
try {
IClassBodyEvaluator cbe = this.compilerFactory.newClassBodyEvaluator();
cbe.setImplementedInterfaces(new Class[] { Runnable.class });
cbe.cook(
""
+ "public void m() { // Implement \"m()\" instead of \"run()\".\n"
+ " System.out.println(\"Got here\");\n"
+ "}"
);
Assert.fail("CompileException expected");
} catch (CompileException ex) {
;
}
}
@Test @SuppressWarnings("unchecked") public void
testFastExpressionEvaluator() throws Exception {
IExpressionEvaluator ee = this.compilerFactory.newExpressionEvaluator();
ee.setImplementedInterfaces(new Class[] { Comparable.class });
ee.setExpressionTypes(new Class[] { int.class });
((Comparable<String>) ee.createFastEvaluator(
"o == null ? 3 : 4",
Comparable.class,
new String[] { "o" }
)).compareTo("");
}
private static final int COUNT = 10000;
@Test public void
testManyEEs() throws Exception {
IExpressionEvaluator ee = this.compilerFactory.newExpressionEvaluator();
String[] expressions = new String[EvaluatorTest.COUNT];
String[][] parameterNames = new String[EvaluatorTest.COUNT][2];
Class<?>[][] parameterTypes = new Class[EvaluatorTest.COUNT][2];
for (int i = 0; i < expressions.length; ++i) {
expressions[i] = "a + b";
parameterNames[i][0] = "a";
parameterNames[i][1] = "b";
parameterTypes[i][0] = int.class;
parameterTypes[i][1] = int.class;
}
ee.setParameters(parameterNames, parameterTypes);
ee.cook(expressions);
Assert.assertEquals(165, ee.evaluate(3 * EvaluatorTest.COUNT / 4, new Object[] { 77, 88 }));
}
@Test public void
testErrorHandler() throws Exception {
MapResourceFinder sourceFinder = new MapResourceFinder();
sourceFinder.addResource("pkg/A.java", (
""
+ "package pkg;\n"
+ "public class A {\n"
+ " void meth() {\n"
+ " return 1;\n"
+ " }\n"
+ "}\n"
));
// Default error handling.
new ExpressionTest("a").assertUncookable("\"a\" is not an rvalue|cannot find symbol.*a");
// Error handler that throws a CompileException.
{
ExpressionTest et = new ExpressionTest("a");
final int[] count = new int[1];
et.setCompileErrorHandler(new ErrorHandler() {
@Override public void
handleError(String message, @Nullable Location location) throws CompileException {
count[0]++;
throw new CompileException(message, location);
}
});
et.assertUncookable("\"a\" is not an rvalue|cannot find symbol.*a");
Assert.assertEquals(1, count[0]);
}
// Error handler that does *not* throw a CompileException.
{
ExpressionTest et = new ExpressionTest("a");
final int[] count = new int[1];
et.setCompileErrorHandler(new ErrorHandler() {
@Override public void handleError(String message, @Nullable Location location) { count[0]++; }
});
et.assertUncookable("failed with 1 errors|error.*while compiling|unknown reason");
Assert.assertTrue(count[0] > 0);
}
}
@Test public void
testAccessingCompilingClass() throws Exception {
ISimpleCompiler sc = this.compilerFactory.newSimpleCompiler();
sc.cook(
""
+ "package test.simple;\n"
+ "public class L0 {\n"
+ " public static class L1 {\n"
+ " public static class L2 { }\n"
+ " } \n"
+ " public Class getL0_1() {\n"
+ " return L0.class;\n"
+ " }\n"
+ " public Class getL0_2() {\n"
+ " return test.simple.L0.class;\n"
+ " }\n"
+ " public Class getL1_1() {\n"
+ " return L1.class;\n"
+ " }\n"
+ " public Class getL1_2() {\n"
+ " return L0.L1.class;\n"
+ " }\n"
+ " public Class getL1_3() {\n"
+ " return test.simple.L0.L1.class;\n"
+ " }\n"
+ " public Class getL2_1() {\n"
+ " return L1.L2.class;\n"
+ " }\n"
+ " public Class getL2_2() {\n"
+ " return L0.L1.L2.class;\n"
+ " }\n"
+ " public Class getL2_3() {\n"
+ " return test.simple.L0.L1.L2.class;\n"
+ " }\n"
+ "}"
);
Class<?>[] exp = new Class[] {
sc.getClassLoader().loadClass("test.simple.L0"),
sc.getClassLoader().loadClass("test.simple.L0$L1"),
sc.getClassLoader().loadClass("test.simple.L0$L1$L2"),
};
Method[] methods = exp[0].getMethods();
Object inst = exp[0].newInstance();
int numTests = 0;
for (Method method : methods) {
for (int j = 0; j < exp.length; ++j) {
if (method.getName().startsWith("getL" + j)) {
Class<?> res = (Class<?>) method.invoke(inst, new Object[0]);
Assert.assertEquals(exp[j], res);
++numTests;
}
}
}
//we count tests just to make sure things didn't go horrifically wrong and
//the above loops become empty
Assert.assertEquals(8, numTests);
}
@Test public void
testDivByZero() throws Exception {
ISimpleCompiler sc = this.compilerFactory.newSimpleCompiler();
sc.cook(
""
+ "package test;\n"
+ "public class Test {\n"
+ " public int runIntDiv() {\n"
+ " return 1 / 0;\n"
+ " }\n"
+ " public int runIntMod() {\n"
+ " return 1 % 0;\n"
+ " }\n"
+ " public long runLongDiv() {\n"
+ " return 1L / 0;\n"
+ " }\n"
+ " public long runLongMod() {\n"
+ " return 1L % 0;\n"
+ " }\n"
+ "}"
);
Class<?> c = sc.getClassLoader().loadClass("test.Test");
Object o = c.newInstance();
for (Method method : c.getMethods()) {
if (method.getName().startsWith("run")) {
try {
Object res = method.invoke(o, new Object[0]);
Assert.fail("Method " + method + " should have failed, but got " + res);
} catch (InvocationTargetException ae) {
Assert.assertTrue(ae.getTargetException() instanceof ArithmeticException);
}
}
}
}
@Test public void
testTrinaryOptimize() throws Exception {
ISimpleCompiler sc = this.compilerFactory.newSimpleCompiler();
sc.cook(
""
+ "package test;\n"
+ "public class Test {\n"
+ " public int runTrue() {\n"
+ " return true ? -1 : 1;\n"
+ " }\n"
+ " public int runFalse() {\n"
+ " return false ? -1 : 1;\n"
+ " }\n"
+ "}"
);
Class<?> c = sc.getClassLoader().loadClass("test.Test");
Method trueMeth = c.getMethod("runTrue");
Method falseMeth = c.getMethod("runFalse");
Object o = c.newInstance();
Assert.assertEquals(-1, trueMeth.invoke(o, new Object[0]));
Assert.assertEquals(1, falseMeth.invoke(o, new Object[0]));
}
public static boolean
compare(double lhs, double rhs, String comp) {
// SUPPRESS CHECKSTYLE StringLiteralEquality:6
if (comp == "==") { return lhs == rhs; }
if (comp == "!=") { return lhs != rhs; }
if (comp == "<") { return lhs < rhs; }
if (comp == "<=") { return lhs <= rhs; }
if (comp == ">") { return lhs > rhs; }
if (comp == ">=") { return lhs >= rhs; }
throw new RuntimeException("Unsupported comparison");
}
public static boolean
compare(float lhs, float rhs, String comp) {
// SUPPRESS CHECKSTYLE StringLiteralEquality:6
if (comp == "==") { return lhs == rhs; }
if (comp == "!=") { return lhs != rhs; }
if (comp == "<") { return lhs < rhs; }
if (comp == "<=") { return lhs <= rhs; }
if (comp == ">") { return lhs > rhs; }
if (comp == ">=") { return lhs >= rhs; }
throw new RuntimeException("Unsupported comparison");
}
@Test public void
testHandlingNaN() throws Exception {
String prog1 = (
""
+ "package test;\n"
+ "public class Test {\n"
+ " public static boolean compare(double lhs, double rhs, String comp) {\n"
+ " if (comp == \"==\") { return lhs == rhs; }\n"
+ " if (comp == \"!=\") { return lhs != rhs; }\n"
+ " if (comp == \"<\" ) { return lhs < rhs; }\n"
+ " if (comp == \"<=\") { return lhs <= rhs; }\n"
+ " if (comp == \">\" ) { return lhs > rhs; }\n"
+ " if (comp == \">=\") { return lhs >= rhs; }\n"
+ " throw new RuntimeException(\"Unsupported comparison\");\n"
+ " }\n"
+ " public static boolean compare(float lhs, float rhs, String comp) {\n"
+ " if (comp == \"==\") { return lhs == rhs; }\n"
+ " if (comp == \"!=\") { return lhs != rhs; }\n"
+ " if (comp == \"<\" ) { return lhs < rhs; }\n"
+ " if (comp == \"<=\") { return lhs <= rhs; }\n"
+ " if (comp == \">\" ) { return lhs > rhs; }\n"
+ " if (comp == \">=\") { return lhs >= rhs; }\n"
+ " throw new RuntimeException(\"Unsupported comparison\");\n"
+ " }\n"
+ "}\n"
);
String prog2 = (
""
+ "package test;\n"
+ "public class Test {\n"
+ " public static boolean compare(double lhs, double rhs, String comp) {\n"
+ " if (comp == \"==\") { if (lhs == rhs) { return true; } else { return false; } }\n"
+ " if (comp == \"!=\") { if (lhs != rhs) { return true; } else { return false; } }\n"
+ " if (comp == \"<\" ) { if (lhs < rhs) { return true; } else { return false; } }\n"
+ " if (comp == \"<=\") { if (lhs <= rhs) { return true; } else { return false; } }\n"
+ " if (comp == \">\" ) { if (lhs > rhs) { return true; } else { return false; } }\n"
+ " if (comp == \">=\") { if (lhs >= rhs) { return true; } else { return false; } }\n"
+ " throw new RuntimeException(\"Unsupported comparison\");\n"
+ " }\n"
+ " public static boolean compare(float lhs, float rhs, String comp) {\n"
+ " if (comp == \"==\") { if (lhs == rhs) { return true; } else { return false; } }\n"
+ " if (comp == \"!=\") { if (lhs != rhs) { return true; } else { return false; } }\n"
+ " if (comp == \"<\" ) { if (lhs < rhs) { return true; } else { return false; } }\n"
+ " if (comp == \"<=\") { if (lhs <= rhs) { return true; } else { return false; } }\n"
+ " if (comp == \">\" ) { if (lhs > rhs) { return true; } else { return false; } }\n"
+ " if (comp == \">=\") { if (lhs >= rhs) { return true; } else { return false; } }\n"
+ " throw new RuntimeException(\"Unsupported comparison\");\n"
+ " }\n"
+ "}\n"
);
String[] progs = new String[] { prog1, prog2 };
for (String prog : progs) {
ISimpleCompiler sc = this.compilerFactory.newSimpleCompiler();
sc.cook(prog);
final Class<?> c = sc.getClassLoader().loadClass("test.Test");
final Method dm = c.getMethod("compare", new Class[] { double.class, double.class, String.class });
final Method fm = c.getMethod("compare", new Class[] { float.class, float.class, String.class });
final Double[][] argss = new Double[][] {
{ new Double(Double.NaN), new Double(Double.NaN) },
{ new Double(Double.NaN), new Double(1.0) },
{ new Double(1.0), new Double(Double.NaN) },
{ new Double(1.0), new Double(2.0) },
{ new Double(2.0), new Double(1.0) },
{ new Double(1.0), new Double(1.0) },
};
String[] operators = new String[] { "==", "!=", "<", "<=", ">", ">=" };
for (String operator : operators) {
for (Double[] args : argss) {
String msg = "\"" + args[0] + " " + operator + " " + args[1] + "\"";
{
boolean exp = EvaluatorTest.compare(
args[0].doubleValue(),
args[1].doubleValue(),
operator
);
Object[] objs = new Object[] { args[0], args[1], operator };
Object actual = dm.invoke(null, objs);
Assert.assertEquals(msg, exp, actual);
}
{
msg = "float: " + msg;
boolean exp = EvaluatorTest.compare(
args[0].floatValue(),
args[1].floatValue(),
operator
);
Object[] objs = new Object[] {
new Float(args[0].floatValue()),
new Float(args[1].floatValue()),
operator
};
Object actual = fm.invoke(null, objs);
Assert.assertEquals(msg, exp, actual);
}
}
}
}
}
@Test public void
test32kBranchLimit() throws Exception {
String preamble = (
""
+ "package test;\n"
+ "public class Test {\n"
+ " public int run() {\n"
+ " int res = 0;\n"
+ " for(int i = 0; i < 2; ++i) {\n"
);
String middle = (
""
+ " ++res;\n"
);
final String postamble = (
""
+ " }\n"
+ " return res;\n"
+ " }\n"
+ "}"
);
int[] repetitionss = new int[] {
1,
10,
100,
Short.MAX_VALUE / 5,
Short.MAX_VALUE / 4,
10930,
10921,
10922,
21837,
21838,
Short.MAX_VALUE / 2,
};
REPETITION: for (int repetitions : repetitionss) {
StringBuilder sb = new StringBuilder();
sb.append(preamble);
for (int j = 0; j < repetitions; ++j) {
sb.append(middle);
}
sb.append(postamble);
ISimpleCompiler sc = this.compilerFactory.newSimpleCompiler();
try {
sc.cook(sb.toString());
} catch (Exception e) {
if (repetitions >= 21838) {
for (Throwable t = e; t != null; t = t.getCause()) {
String message = t.getMessage();
if (message.contains("beyond 64 KB") || message.contains("code too large")) continue REPETITION;
}
}
throw new AssertionError("repetitions=" + repetitions, e);
}
Object res;
try {
Class<?> c = sc.getClassLoader().loadClass("test.Test");
Method m = c.getDeclaredMethod("run", new Class[0]);
Object o = c.newInstance();
res = m.invoke(o, new Object[0]);
} catch (Error e) {
throw new Error("repetitions=" + repetitions, e);
}
Assert.assertEquals(2 * repetitions, res);
}
}
@Test public void
test64kConstantPool() throws Exception {
/* == expected contant pool ==
( 0) fake entry
[ 1] ConstantUtf8Info "test/Test"
[ 2] ConstantClassInfo 1
[ 3] ConstantUtf8Info "java/lang/Object"
[ 4] ConstantClassInfo 3
[ 5] ConstantUtf8Info "<init>"
[ 6] ConstantUtf8Info "()V"
[ 7] ConstantNameAndTypeInfo 5
[ 8] ConstantMethodrefInfo 4
[ 9] ConstantUtf8Info "Code"
[10] ConstantUtf8Info "_v0"
[11] ConstantUtf8Info "Z"
[12] ConstantUtf8Info "_v1"
[13] ConstantUtf8Info "_v2"
...
[65534] ConstantUtf8Info "_v65523"
*/
final int[] repetitionss = new int[] { 1, 100, 65517, 65525 };
final boolean[] isCookables = new boolean[] { true, true, true, false };
for (int i = 0; i < repetitionss.length; i++) {
final int repetitions = repetitionss[i];
final boolean isCookable = isCookables[i];
final String cu;
{
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
pw.printf("package test;%n");
pw.printf("public class Test {%n");
for (int j = 0; j < repetitions; ++j) pw.printf(" boolean _v%d;%n", j);
pw.printf("}%n");
pw.flush();
cu = sw.toString();
}
ISimpleCompiler sc = this.compilerFactory.newSimpleCompiler();
if (isCookable) {
sc.cook(cu);
Assert.assertNotNull(sc.getClassLoader().loadClass("test.Test").newInstance());
} else {
this.assertCompilationUnitUncookable(
cu,
"too many constants \\(compiler.err.limit.pool\\)|grown past JVM limit of 0xFFFF"
);
}
}
}
@Test public void
testManyArgumentsCall() throws Exception {
final String preamble = (
""
+ "package test;\n"
+ "public class Test {\n"
+ " double test(\n"
);
final String middle1 = (
""
+ " double d_{0,number,#},\n"
);
final String middle2 = (
""
+ " public double run() {\n"
+ " boolean b = false;\n"
+ " double d = b ? 2.3D : test(\n"
);
final String postamble = (
""
+ " );\n"
+ " return d;\n"
+ " }\n"
+ "}"
);
int[] repetitionss = new int[] { 1, 16, 127 };
for (int repetitions : repetitionss) {
StringBuilder sb = new StringBuilder();
sb.append(preamble);
for (int j = 0; j < repetitions - 1; ++j) {
sb.append(MessageFormat.format(middle1, new Object[] { j }));
}
sb.append("double d_" + (repetitions - 1) + ") { return 1.2D; }\n");
sb.append(middle2);
for (int j = 0; j < repetitions - 1; ++j) {
sb.append("0, ");
}
sb.append("0");
sb.append(postamble);
ISimpleCompiler sc = this.compilerFactory.newSimpleCompiler();
sc.cook(sb.toString());
Class<?> c = sc.getClassLoader().loadClass("test.Test");
Method m = c.getDeclaredMethod("run", new Class[0]);
Object o = c.newInstance();
Object res = m.invoke(o, new Object[0]);
Assert.assertEquals(1.2D, res);
}
}
@Test public void
test64kOperandStackHeight() throws Exception {
String preamble = (
""
+ "package test;\n"
+ "public class Test {\n"
+ " public double run() {\n"
+ " double d = 1;\n"
+ " double r = "
);
final String epilog = (
";\n"
+ " return r;\n"
+ " }\n"
+ "}"
);
// If more than 299, then we'd have to increase the JVM stack size.
for (int repetitions : new int[] { 5, 50, 260 }) {
StringBuilder sb = new StringBuilder().append(preamble);
for (int j = 0; j < repetitions; ++j) sb.append("+ (d");
for (int j = 0; j < repetitions; ++j) sb.append(")");
sb.append(epilog);
ISimpleCompiler sc = this.compilerFactory.newSimpleCompiler();
sc.cook(sb.toString());
Class<?> c = sc.getClassLoader().loadClass("test.Test");
Method m = c.getDeclaredMethod("run", new Class[0]);
Object o = c.newInstance();
Object res = m.invoke(o);
Assert.assertEquals((double) repetitions, res);
}
}
@Test public void
testHugeIntArray() throws Exception {
String preamble = (
""
+ "package test;\n"
+ "public class Test {\n"
+ " public int[] run() {\n"
+ " return 1.0 > 2.0 ? null : new int[] {"
);
String middle = (
""
+ " 123,"
);
final String postamble = (
""
+ " };\n"
+ " }\n"
+ "}"
);
int[] repetitionss = new int[] { 1, 10, 8192 };
for (int repetitions : repetitionss) {
StringBuilder sb = new StringBuilder();
sb.append(preamble);
for (int j = 0; j < repetitions; ++j) {
sb.append(middle);
}
sb.append(postamble);
ISimpleCompiler sc = this.compilerFactory.newSimpleCompiler();
sc.cook(sb.toString());
Class<?> c = sc.getClassLoader().loadClass("test.Test");
Object o = c.newInstance();
Assert.assertNotNull(o);
}
}
@Test public void
testStaticFieldAccess() throws Exception {
this.assertCompilationUnitCookable((
""
+ "package test;\n"
+ "public class Test {\n"
+ " public static class Inner {\n"
+ " public static int i = 0;\n"
+ " }\n"
+ " public int runTest(Inner in) {\n"
+ " return in.i;\n"
+ " }\n"
+ "}"
));
}
@Test public void
testWideInstructions() throws Exception {
String preamble = (
""
+ "package test;\n"
+ "public class Test {\n"
+ " public static String run() {\n"
);
String middle = (
""
+ " Object o_{0,number,#}; int i_{0,number,#};\n"
);
final String postamble = (
""
+ " int i = (int)0; ++i; i = (int)(i*i);\n"
+ " double d = (double)0.0; ++d; d = (double)(d*d);\n"
+ " float f = (float)0.0; ++f; f = (float)(f*f);\n"
+ " short s = (short)0; ++s; s = (short)(s*s);\n"
+ " long l = (long)0; ++l; l = (long)(l*l);\n"
+ " boolean b = false; b = !b;\n"
+ " Object o = \"hi\"; o = o.toString();\n"
+ " String res = o.toString() +\" \"+ i +\" \"+ d +\" \"+ f +\" \"+ s +\" \"+ l +\" \"+ b;\n"
+ " try { res = res + \" try\"; } finally { res = res + \" finally\"; }\n"
+ " return res;\n"
+ " };\n"
+ "}"
);
int[] repetitionss = new int[] { 1, 128, };
for (int repetitions : repetitionss) {
StringBuilder sb = new StringBuilder();
sb.append(preamble);
for (int j = 0; j < repetitions; ++j) {
sb.append(MessageFormat.format(middle, new Object[] { j }));
}
sb.append(postamble);
ISimpleCompiler sc = this.compilerFactory.newSimpleCompiler();
sc.cook(sb.toString());
Class<?> c = sc.getClassLoader().loadClass("test.Test");
Method m = c.getDeclaredMethod("run", new Class[0]);
Object o = m.invoke(null, new Object[0]);
Assert.assertEquals("hi 1 1.0 1.0 1 1 true try finally", o);
}
}
@Test public void
testInstanceOf() throws Exception {
String test = (
""
+ "package test;\n"
+ "public class Test {\n"
+ " public static boolean run(String o) {\n"
+ " return o instanceof String;"
+ " };\n"
+ " public static boolean run(Object o) {\n"
+ " return o instanceof String;"
+ " };\n"
+ " public static boolean run() {\n"
+ " return null instanceof String;"
+ " };\n"
+ "}"
);
ISimpleCompiler sc = this.compilerFactory.newSimpleCompiler();
sc.cook(test);
Class<?> c = sc.getClassLoader().loadClass("test.Test");
Method m0 = c.getDeclaredMethod("run", new Class[] {});
Assert.assertEquals(false, m0.invoke(null, new Object[0]));
Method mStr = c.getDeclaredMethod("run", new Class[] { String.class });
Method mObj = c.getDeclaredMethod("run", new Class[] { Object.class });
Assert.assertEquals(true, mObj.invoke(null, new Object[] { "" }));
Assert.assertEquals(false, mObj.invoke(null, new Object[] { 1 }));
Assert.assertEquals(false, mObj.invoke(null, new Object[] { null }));
Assert.assertEquals(true, mStr.invoke(null, new Object[] { "" }));
Assert.assertEquals(false, mStr.invoke(null, new Object[] { null }));
}
@Test public void
testOverrideVisibility() throws Exception {
// so should this
this.assertCompilationUnitMainReturnsTrue((
""
+ "package test;\n"
+ "public class Test {\n"
+ " public static boolean main() {\n"
+ " return \"A\".equals(test.OverridesWithDifferingVisibility.test(new Object[] { \"asdf\"} ));\n"
+ " }\n"
+ "}\n"
+ "\n"
+ "public\n"
+ "class OverridesWithDifferingVisibility {\n"
+ "\n"
+ " public static String test(Object o) { return \"A\"; }\n"
+ " private static String test(Object[] arr) { return \"B\"; }\n"
+ "}"
), "test.Test");
}
@Test public void
testCovariantReturns() throws Exception {
this.assertCompilationUnitCookable(
""
+ "package test;\n"
+ "public class Test extends CovariantReturns {\n"
+ " @Override public Test overrideMe() { return this; }\n"
+ "}\n"
+ "public abstract\n"
+ "class CovariantReturns {\n"
+ " public abstract CovariantReturns overrideMe();\n"
+ "}"
);
this.assertCompilationUnitUncookable(
""
+ "package test;\n"
+ "public class Test2 extends CovariantReturns {\n"
+ " public Integer overrideMe() { return null; }\n"
+ "}\n"
+ "public abstract\n"
+ "class CovariantReturns {\n"
+ " public abstract CovariantReturns overrideMe();\n"
+ "}"
);
}
@Test public void
testNonExistentImport() throws Exception {
this.assertCompilationUnitUncookable(
"import does.not.Exist; public class Test { private final Exist e = null; }"
);
this.assertCompilationUnitUncookable("import does.not.Exist; public class Test { }");
}
@Test public void
testAnonymousFieldInitializedByCapture() throws Exception {
ISimpleCompiler sc = this.compilerFactory.newSimpleCompiler();
sc.cook(
""
+ "public class Top {\n"
+ " public Runnable get() {\n"
+ " final String foo = \"foo\";\n"
+ " final String cow = \"cow\";\n"
+ " final String moo = \"moo\";\n"
+ " return new Runnable() {\n"
+ " @Override public void run() {\n"
+ " if (bar == null) {\n"
+ " throw new RuntimeException(\"bar is NULL\");\n"
+ " }\n"
+ " }\n"
+ " private String bar = foo;\n"
+ " private String[] cowparts = { moo, cow };\n"
+ " };\n"
+ " }\n"
+ "}\n"
);
Class<?> topClass = sc.getClassLoader().loadClass("Top");
Method get = topClass.getDeclaredMethod("get", new Class[0]);
Object t = topClass.newInstance();
Object res = get.invoke(t, new Object[0]);
((Runnable) res).run();
}
@Test public void
testNamedFieldInitializedByCapture() throws Exception {
ISimpleCompiler sc = this.compilerFactory.newSimpleCompiler();
sc.cook(
""
+ "public class Top {\n"
+ " public Runnable get() {\n"
+ " final String foo = \"foo\";\n"
+ " final String cow = \"cow\";\n"
+ " final String moo = \"moo\";\n"
+ " class R implements Runnable {\n"
+ " @Override public void run() {\n"
+ " if (bar == null) {\n"
+ " throw new RuntimeException();\n"
+ " }\n"
+ " }\n"
+ " private String bar = foo;\n"
+ " private String[] cowparts = { moo, cow };\n"
+ " }\n"
+ " return new R();"
+ " }\n"
+ "}\n"
);
Class<?> topClass = sc.getClassLoader().loadClass("Top");
Method get = topClass.getDeclaredMethod("get", new Class[0]);
Object t = topClass.newInstance();
Object res = get.invoke(t, new Object[0]);
((Runnable) res).run();
}
@Test public void
testAbstractGrandParentsWithCovariantReturns() throws Exception {
this.assertCompilationUnitCookable(
""
+ "public class Top {\n"
+ " private static class IndentPrintWriter extends java.io.PrintWriter { "
+ " public IndentPrintWriter(java.io.OutputStream os) { super(os); }"
+ " }"
+ "}"
);
}
@Test public void
testStringBuilderLength() throws Exception {
ISimpleCompiler sc = this.compilerFactory.newSimpleCompiler();
sc.cook(
""
+ "public class Top {\n"
+ " public int len(StringBuilder sb) { return sb.length(); }\n"
+ "}\n"
);
Class<?> topClass = sc.getClassLoader().loadClass("Top");
Method get = topClass.getDeclaredMethod("len", new Class[] { StringBuilder.class });
Object t = topClass.newInstance();
StringBuilder sb = new StringBuilder();
Assert.assertEquals(sb.length(), get.invoke(t, new Object[] { sb }));
sb.append("asdf");
Assert.assertEquals(sb.length(), get.invoke(t, new Object[] { sb }));
sb.append("qwer");
Assert.assertEquals(sb.length(), get.invoke(t, new Object[] { sb }));
}
@Test public void
testCovariantClone() throws Exception {
ISimpleCompiler sc = this.compilerFactory.newSimpleCompiler();
sc.cook(
""
+ "package covariant_clone;\n"
+ "\n"
+ "public class Child extends Middle implements java.lang.Cloneable {\n"
+ " @Override public Child clone() throws java.lang.CloneNotSupportedException {\n"
+ " return new Child();\n"
+ " }\n"
+ " @Override public Child other(long i, Object o) {\n"
+ " return this;\n"
+ " }\n"
+ "}\n"
+ "\n"
+ "public\n"
+ "class Middle extends Base {\n"
+ "\n"
+ " public Base\n"
+ " cloneWithOutArguments() {\n"
+ " try {\n"
+ " return this.clone();\n"
+ " } catch (CloneNotSupportedException e) {\n"
+ " throw new RuntimeException(\n"
+ " \"Clone not supported on class: \" + this.getClass().getName(),\n"
+ " e\n"
+ " );\n"
+ " }\n"
+ " }\n"
+ "\n"
+ " public Middle\n"
+ " cloneWithArguments() {\n"
+ " return this.other(1, null);\n"
+ " }\n"
+ "\n"
+ " @Override public Middle\n"
+ " other(long i, Object o) {\n"
+ " throw new RuntimeException(\"Middle() called\");\n"
+ " }\n"
+ "}\n"
+ "\n"
+ "public abstract\n"
+ "class Base implements Cloneable, CloneableData {\n"
+ "\n"
+ " /**\n"
+ " * Clone this tuple, the new tuple will not share any buffers or data with the original.\n"
+ " * @return A copy of this Tuple\n"
+ " */\n"
+ " @Override public Base\n"
+ " clone() throws CloneNotSupportedException {\n"
+ " //subclasses must implement\n"
+ " throw new CloneNotSupportedException();\n"
+ " }\n"
+ "\n"
+ " public Base\n"
+ " other(long i, Object o) {\n"
+ " throw new RuntimeException(\"Base.other() called\");\n"
+ " }\n"
+ "}\n"
+ "\n"
+ "public\n"
+ "interface CloneableData extends Cloneable {\n"
+ " CloneableData clone() throws CloneNotSupportedException;\n"
+ "}"
);
// calling clone directly here would work, we need to trigger a call into the
// covariant version of clone from a less described version of it.
Class<?> topClass = sc.getClassLoader().loadClass("covariant_clone.Child");
Method foo = topClass.getMethod("cloneWithArguments");
Method bar = topClass.getMethod("cloneWithOutArguments");
Object t = topClass.newInstance();
Assert.assertNotNull(foo.invoke(t, new Object[0]));
Assert.assertNotNull(bar.invoke(t, new Object[0]));
}
@Test public void
testBaseClassAccess() throws Exception {
this.assertCompilationUnitCookable(
""
+ "class top extends other_package.ScopingRules {\n"
+ " class Inner extends other_package.ScopingRules.ProtectedInner {\n"
+ " public void test() {\n"
+ " publicMethod();\n"
+ " }\n"
+ " }\n"
+ "\n"
+ " public void test() {\n"
+ " Inner i = new Inner();\n"
+ " i.publicMethod();\n"
+ " }\n"
+ "}\n"
);
}
@Test public void
testNullComparator() throws Exception {
this.assertCompilationUnitCookable(
""
+ "class Test {\n"
+ " public void test() {\n"
+ " if (null == null) {\n"
+ " // success\n"
+ " } else if (null != null) {\n"
+ " throw new RuntimeException();\n"
+ " }\n"
+ " }\n"
+ "}\n"
);
}
@Test public void
testDiagnosticLineNumbers() throws Exception {
this.assertClassBodyUncookable((
""
+ "public static void\n"
+ "main(String[] args) {\n"
+ " System\n"
+ "\n"
+ "\n"
+ " .\n"
+ "\n"
+ "\n"
+ " out\n"
+ "\n"
+ "\n"
+ " .\n" // Line 12
+ "\n"
+ "\n"
+ " XXXprintln\n" // Line 15
+ "\n"
+ "\n"
+ " (\"Hello world\");\n"
+ "}\n"
), 12, 15);
this.assertScriptUncookable((
""
+ "System.out\n"
+ ".XXXprintln\n"
+ "(\"Hello world\");\n"
), 2);
this.assertExpressionUncookable((
""
+ "\"abc\"\n"
+ "+\n"
+ "x\n" // <= Line 3, unknown variable
), 3);
}
@Test public void
testAnyType1() throws Exception {
@SuppressWarnings("deprecation") Class<?> anyType = IExpressionEvaluator.ANY_TYPE;
IExpressionEvaluator ee = this.compilerFactory.newExpressionEvaluator();
ee.setExpressionType(anyType);
ee.cook("3");
Assert.assertEquals(3, ee.evaluate());
}
@Test public void
testAnyType2() throws Exception {
@SuppressWarnings("deprecation") Class<?> anyType = IExpressionEvaluator.ANY_TYPE;
IExpressionEvaluator ee = this.compilerFactory.newExpressionEvaluator();
ee.setExpressionType(anyType);
ee.cook("\"HELLO\"");
Assert.assertEquals("HELLO", ee.evaluate());
}
@Test public void
testAnyType3() throws Exception {
IExpressionEvaluator ee = this.compilerFactory.newExpressionEvaluator();
ee.setExpressionType(Object.class);
ee.cook("3");
Assert.assertEquals(3, ee.evaluate());
}
@Test public void
testAnyType4() throws Exception {
IExpressionEvaluator ee = this.compilerFactory.newExpressionEvaluator();
ee.setExpressionType(Object.class);
ee.cook("\"HELLO\"");
Assert.assertEquals("HELLO", ee.evaluate());
}
@Test public void
testMultipleExpressions() throws Exception {
IExpressionEvaluator ee = this.compilerFactory.newExpressionEvaluator();
// ee.setStaticMethod(false);
ee.setStaticMethod(new boolean[] { false, false });
ee.cook("9*3;7+1".split(";"));
}
@Test public void
testSimpleLocalMethod() throws Exception {
// The JDK implementation does not support "local methods" (see JAVADOC of IScriptEvaluator).
if (this.isJdk) return;
final IScriptEvaluator se = this.compilerFactory.newScriptEvaluator();
se.setReturnType(int.class);
se.cook((
""
+ "return meth();\n"
+ "static int meth() { return 7; }\n"
));
Assert.assertEquals(7, se.evaluate());
}
@Test public void
testOverlappingLocalMethods1() throws Exception {
// The JDK implementation does not support "local methods" (see JAVADOC of IScriptEvaluator).
if (this.isJdk) return;
final IScriptEvaluator se = this.compilerFactory.newScriptEvaluator();
se.cook(new String[] {
"void meth1() {}\n",
"void meth2() {}\n"
});
}
@Test public void
testOverlappingLocalMethods2() throws Exception {
// The JDK implementation does not support "local methods" (see JAVADOC of IScriptEvaluator).
if (this.isJdk) return;
final IScriptEvaluator se = this.compilerFactory.newScriptEvaluator();
try {
se.cook(new String[] { // TWO scripts that declare a local method with the same name.
"void meth() {}\n",
"void meth() {}\n"
});
Assert.fail("Compilation exception expected");
} catch (CompileException ce) {
Assert.assertTrue(
ce.getMessage(),
ce.getMessage().contains("Redeclaration")
);
}
}
}