Merge pull request #18410 from rmuir/painless_parser_performance_bug

painless: fix insanely slow compilation
This commit is contained in:
Robert Muir 2016-05-17 12:18:20 -04:00
commit ee6d29b342
7 changed files with 1227 additions and 1323 deletions

View File

@ -189,7 +189,7 @@ POST hockey/player/1/_update
{ {
"script": { "script": {
"lang": "painless", "lang": "painless",
"inline": "ctx._source.last = params.last ctx._source.nick = params.nick", "inline": "ctx._source.last = params.last; ctx._source.nick = params.nick",
"params": { "params": {
"last": "gaudreau", "last": "gaudreau",
"nick": "hockey" "nick": "hockey"

View File

@ -28,15 +28,15 @@ source
statement statement
: IF LP expression RP block ( ELSE block )? # if : IF LP expression RP block ( ELSE block )? # if
| WHILE LP expression RP ( block | empty ) # while | WHILE LP expression RP ( block | empty ) # while
| DO block WHILE LP expression RP SEMICOLON? # do | DO block WHILE LP expression RP ( SEMICOLON | EOF ) # do
| FOR LP initializer? SEMICOLON expression? SEMICOLON afterthought? RP ( block | empty ) # for | FOR LP initializer? SEMICOLON expression? SEMICOLON afterthought? RP ( block | empty ) # for
| declaration SEMICOLON? # decl | declaration ( SEMICOLON | EOF ) # decl
| CONTINUE SEMICOLON? # continue | CONTINUE ( SEMICOLON | EOF ) # continue
| BREAK SEMICOLON? # break | BREAK ( SEMICOLON | EOF ) # break
| RETURN expression SEMICOLON? # return | RETURN expression ( SEMICOLON | EOF ) # return
| TRY block trap+ # try | TRY block trap+ # try
| THROW expression SEMICOLON? # throw | THROW expression ( SEMICOLON | EOF ) # throw
| expression SEMICOLON? # expr | expression ( SEMICOLON | EOF ) # expr
; ;
block block

View File

@ -19,161 +19,60 @@
package org.elasticsearch.painless; package org.elasticsearch.painless;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
public class NoSemiColonTests extends ScriptTestCase { public class NoSemiColonTests extends ScriptTestCase {
public void testIfStatement() {
assertEquals(1, exec("int x = 5 if (x == 5) return 1 return 0"));
assertEquals(0, exec("int x = 4 if (x == 5) return 1 else return 0"));
assertEquals(2, exec("int x = 4 if (x == 5) return 1 else if (x == 4) return 2 else return 0"));
assertEquals(1, exec("int x = 4 if (x == 5) return 1 else if (x == 4) return 1 else return 0"));
assertEquals(3, exec(
"int x = 5\n" +
"if (x == 5) {\n" +
" int y = 2\n" +
" \n" +
" if (y == 2) {\n" +
" x = 3\n" +
" }\n" +
" \n" +
"}\n" +
"\n" +
"return x\n"));
}
public void testWhileStatement() {
assertEquals("aaaaaa", exec("String c = \"a\" int x while (x < 5) { ++x c += \"a\" } return c"));
Object value = exec(
" byte[][] b = new byte[5][5] \n" +
" byte x = 0, y \n" +
" \n" +
" while (x < 5) { \n" +
" y = 0 \n" +
" \n" +
" while (y < 5) { \n" +
" b[x][y] = (byte)(x*y) \n" +
" ++y \n" +
" } \n" +
" \n" +
" ++x \n" +
" } \n" +
" \n" +
" return b \n");
byte[][] b = (byte[][])value;
for (byte x = 0; x < 5; ++x) {
for (byte y = 0; y < 5; ++y) {
assertEquals(x*y, b[x][y]);
}
}
}
public void testDoWhileStatement() {
assertEquals("aaaaaa", exec("String c = \"a\" int x do { c += \"a\"; ++x } while (x < 5) return c"));
Object value = exec(
" long[][] l = new long[5][5] \n" +
" long x = 0, y \n" +
" \n" +
" do { \n" +
" y = 0 \n" +
" \n" +
" do { \n" +
" l[(int)x][(int)y] = x*y; \n" +
" ++y \n" +
" } while (y < 5) \n" +
" \n" +
" ++x \n" +
" } while (x < 5) \n" +
" \n" +
" return l \n");
long[][] l = (long[][])value;
for (long x = 0; x < 5; ++x) {
for (long y = 0; y < 5; ++y) {
assertEquals(x*y, l[(int)x][(int)y]);
}
}
}
public void testForStatement() {
assertEquals("aaaaaa", exec("String c = \"a\" for (int x = 0; x < 5; ++x) c += \"a\" return c"));
Object value = exec(
" int[][] i = new int[5][5] \n" +
" for (int x = 0; x < 5; ++x) { \n" +
" for (int y = 0; y < 5; ++y) { \n" +
" i[x][y] = x*y \n" +
" } \n" +
" } \n" +
" \n" +
" return i \n");
int[][] i = (int[][])value;
for (int x = 0; x < 5; ++x) {
for (int y = 0; y < 5; ++y) {
assertEquals(x*y, i[x][y]);
}
}
}
public void testDeclarationStatement() { public void testDeclarationStatement() {
assertEquals((byte)2, exec("byte a = 2 return a")); assertEquals((byte)2, exec("byte a = 2; return a"));
assertEquals((short)2, exec("short a = 2 return a")); assertEquals((short)2, exec("short a = 2; return a"));
assertEquals((char)2, exec("char a = 2 return a")); assertEquals((char)2, exec("char a = 2; return a"));
assertEquals(2, exec("int a = 2 return a")); assertEquals(2, exec("int a = 2; return a"));
assertEquals(2L, exec("long a = 2 return a")); assertEquals(2L, exec("long a = 2; return a"));
assertEquals(2F, exec("float a = 2 return a")); assertEquals(2F, exec("float a = 2; return a"));
assertEquals(2.0, exec("double a = 2 return a")); assertEquals(2.0, exec("double a = 2; return a"));
assertEquals(false, exec("boolean a = false return a")); assertEquals(false, exec("boolean a = false; return a"));
assertEquals("string", exec("String a = \"string\" return a")); assertEquals("string", exec("String a = \"string\"; return a"));
assertEquals(HashMap.class, exec("Map<String, Object> a = new HashMap<String, Object>() return a").getClass()); assertEquals(HashMap.class, exec("Map<String, Object> a = new HashMap<String, Object>(); return a").getClass());
assertEquals(byte[].class, exec("byte[] a = new byte[1] return a").getClass()); assertEquals(byte[].class, exec("byte[] a = new byte[1]; return a").getClass());
assertEquals(short[].class, exec("short[] a = new short[1] return a").getClass()); assertEquals(short[].class, exec("short[] a = new short[1]; return a").getClass());
assertEquals(char[].class, exec("char[] a = new char[1] return a").getClass()); assertEquals(char[].class, exec("char[] a = new char[1]; return a").getClass());
assertEquals(int[].class, exec("int[] a = new int[1] return a").getClass()); assertEquals(int[].class, exec("int[] a = new int[1]; return a").getClass());
assertEquals(long[].class, exec("long[] a = new long[1] return a").getClass()); assertEquals(long[].class, exec("long[] a = new long[1]; return a").getClass());
assertEquals(float[].class, exec("float[] a = new float[1] return a").getClass()); assertEquals(float[].class, exec("float[] a = new float[1]; return a").getClass());
assertEquals(double[].class, exec("double[] a = new double[1] return a").getClass()); assertEquals(double[].class, exec("double[] a = new double[1]; return a").getClass());
assertEquals(boolean[].class, exec("boolean[] a = new boolean[1] return a").getClass()); assertEquals(boolean[].class, exec("boolean[] a = new boolean[1]; return a").getClass());
assertEquals(String[].class, exec("String[] a = new String[1] return a").getClass()); assertEquals(String[].class, exec("String[] a = new String[1]; return a").getClass());
assertEquals(Map[].class, exec("Map<String,Object>[] a = new Map<String,Object>[1] return a").getClass()); assertEquals(Map[].class, exec("Map<String,Object>[] a = new Map<String,Object>[1]; return a").getClass());
assertEquals(byte[][].class, exec("byte[][] a = new byte[1][2] return a").getClass()); assertEquals(byte[][].class, exec("byte[][] a = new byte[1][2]; return a").getClass());
assertEquals(short[][][].class, exec("short[][][] a = new short[1][2][3] return a").getClass()); assertEquals(short[][][].class, exec("short[][][] a = new short[1][2][3]; return a").getClass());
assertEquals(char[][][][].class, exec("char[][][][] a = new char[1][2][3][4] return a").getClass()); assertEquals(char[][][][].class, exec("char[][][][] a = new char[1][2][3][4]; return a").getClass());
assertEquals(int[][][][][].class, exec("int[][][][][] a = new int[1][2][3][4][5] return a").getClass()); assertEquals(int[][][][][].class, exec("int[][][][][] a = new int[1][2][3][4][5]; return a").getClass());
assertEquals(long[][].class, exec("long[][] a = new long[1][2] return a").getClass()); assertEquals(long[][].class, exec("long[][] a = new long[1][2]; return a").getClass());
assertEquals(float[][][].class, exec("float[][][] a = new float[1][2][3] return a").getClass()); assertEquals(float[][][].class, exec("float[][][] a = new float[1][2][3]; return a").getClass());
assertEquals(double[][][][].class, exec("double[][][][] a = new double[1][2][3][4] return a").getClass()); assertEquals(double[][][][].class, exec("double[][][][] a = new double[1][2][3][4]; return a").getClass());
assertEquals(boolean[][][][][].class, exec("boolean[][][][][] a = new boolean[1][2][3][4][5] return a").getClass()); assertEquals(boolean[][][][][].class, exec("boolean[][][][][] a = new boolean[1][2][3][4][5]; return a").getClass());
assertEquals(String[][].class, exec("String[][] a = new String[1][2] return a").getClass()); assertEquals(String[][].class, exec("String[][] a = new String[1][2]; return a").getClass());
assertEquals(Map[][][].class, exec("Map<String,Object>[][][] a = new Map<String,Object>[1][2][3] return a").getClass()); assertEquals(Map[][][].class, exec("Map<String,Object>[][][] a = new Map<String,Object>[1][2][3]; return a").getClass());
} }
public void testContinueStatement() { public void testExpression() {
assertEquals(9, exec("int x = 0, y = 0 while (x < 10) { ++x if (x == 1) continue ++y } return y")); assertEquals(10, exec("10"));
} assertEquals(10, exec("5 + 5"));
assertEquals(10, exec("5 + 5"));
public void testBreakStatement() { assertEquals(10, exec("params.param == 'yes' ? 10 : 5", Collections.singletonMap("param", "yes")));
assertEquals(4, exec("int x = 0, y = 0 while (x < 10) { ++x if (x == 5) break ++y } return y"));
} }
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public void testReturnStatement() { public void testReturnStatement() {
assertEquals(10, exec("return 10")); assertEquals(10, exec("return 10"));
assertEquals(5, exec("int x = 5 return x")); assertEquals(5, exec("int x = 5; return x"));
assertEquals(4, exec("int[] x = new int[2] x[1] = 4 return x[1]")); assertEquals(4, exec("int[] x = new int[2]; x[1] = 4; return x[1]"));
assertEquals(5, ((short[])exec("short[] s = new short[3] s[1] = 5 return s"))[1]); assertEquals(5, ((short[])exec("short[] s = new short[3]; s[1] = 5; return s"))[1]);
assertEquals(10, ((Map)exec("Map<String,Object> s = new HashMap< String,Object>() s.put(\"x\", 10) return s")).get("x")); assertEquals(10, ((Map)exec("Map<String,Object> s = new HashMap< String,Object>(); s.put(\"x\", 10); return s")).get("x"));
} }
} }

View File

@ -19,6 +19,8 @@
package org.elasticsearch.painless; package org.elasticsearch.painless;
import java.util.Locale;
public class StringTests extends ScriptTestCase { public class StringTests extends ScriptTestCase {
public void testAppend() { public void testAppend() {
@ -63,6 +65,21 @@ public class StringTests extends ScriptTestCase {
assertEquals("cat" + "cat", exec("String s = 'cat'; return s + s;")); assertEquals("cat" + "cat", exec("String s = 'cat'; return s + s;"));
} }
public void testAppendMultiple() {
assertEquals("cat" + true + "abc" + null, exec("String s = \"cat\"; return s + true + 'abc' + null;"));
}
public void testAppendMany() {
StringBuilder script = new StringBuilder("String s = \"cat\"; return s");
StringBuilder result = new StringBuilder("cat");
for (int i = 0; i < 200 /* indy limit */ + 10; i++) {
final String s = String.format(Locale.ROOT, "%03d", i);
script.append(" + '").append(s).append("'.toString()");
result.append(s);
}
assertEquals(result.toString(), exec(script.toString()));
}
public void testStringAPI() { public void testStringAPI() {
assertEquals("", exec("return new String();")); assertEquals("", exec("return new String();"));
assertEquals('x', exec("String s = \"x\"; return s.charAt(0);")); assertEquals('x', exec("String s = \"x\"; return s.charAt(0);"));
@ -127,8 +144,8 @@ public class StringTests extends ScriptTestCase {
assertEquals("c", exec("return (String)(char)\"c\"")); assertEquals("c", exec("return (String)(char)\"c\""));
assertEquals("c", exec("return (String)(char)'c'")); assertEquals("c", exec("return (String)(char)'c'"));
assertEquals('c', exec("String s = \"c\" (char)s")); assertEquals('c', exec("String s = \"c\"; (char)s"));
assertEquals('c', exec("String s = 'c' (char)s")); assertEquals('c', exec("String s = 'c'; (char)s"));
try { try {
assertEquals("cc", exec("return (String)(char)\"cc\"")); assertEquals("cc", exec("return (String)(char)\"cc\""));
@ -145,14 +162,14 @@ public class StringTests extends ScriptTestCase {
} }
try { try {
assertEquals('c', exec("String s = \"cc\" (char)s")); assertEquals('c', exec("String s = \"cc\"; (char)s"));
fail(); fail();
} catch (final ClassCastException cce) { } catch (final ClassCastException cce) {
assertTrue(cce.getMessage().contains("Cannot cast [String] with length greater than one to [char].")); assertTrue(cce.getMessage().contains("Cannot cast [String] with length greater than one to [char]."));
} }
try { try {
assertEquals('c', exec("String s = 'cc' (char)s")); assertEquals('c', exec("String s = 'cc'; (char)s"));
fail(); fail();
} catch (final ClassCastException cce) { } catch (final ClassCastException cce) {
assertTrue(cce.getMessage().contains("Cannot cast [String] with length greater than one to [char].")); assertTrue(cce.getMessage().contains("Cannot cast [String] with length greater than one to [char]."));
@ -163,8 +180,8 @@ public class StringTests extends ScriptTestCase {
assertEquals("c", exec("return (String)(Character)\"c\"")); assertEquals("c", exec("return (String)(Character)\"c\""));
assertEquals("c", exec("return (String)(Character)'c'")); assertEquals("c", exec("return (String)(Character)'c'"));
assertEquals('c', exec("String s = \"c\" (Character)s")); assertEquals('c', exec("String s = \"c\"; (Character)s"));
assertEquals('c', exec("String s = 'c' (Character)s")); assertEquals('c', exec("String s = 'c'; (Character)s"));
try { try {
assertEquals("cc", exec("return (String)(Character)\"cc\"")); assertEquals("cc", exec("return (String)(Character)\"cc\""));
@ -181,14 +198,14 @@ public class StringTests extends ScriptTestCase {
} }
try { try {
assertEquals('c', exec("String s = \"cc\" (Character)s")); assertEquals('c', exec("String s = \"cc\"; (Character)s"));
fail(); fail();
} catch (final ClassCastException cce) { } catch (final ClassCastException cce) {
assertTrue(cce.getMessage().contains("Cannot cast [String] with length greater than one to [Character].")); assertTrue(cce.getMessage().contains("Cannot cast [String] with length greater than one to [Character]."));
} }
try { try {
assertEquals('c', exec("String s = 'cc' (Character)s")); assertEquals('c', exec("String s = 'cc'; (Character)s"));
fail(); fail();
} catch (final ClassCastException cce) { } catch (final ClassCastException cce) {
assertTrue(cce.getMessage().contains("Cannot cast [String] with length greater than one to [Character].")); assertTrue(cce.getMessage().contains("Cannot cast [String] with length greater than one to [Character]."));

View File

@ -90,7 +90,7 @@ public class WhenThingsGoWrongTests extends ScriptTestCase {
"The maximum number of statements that can be executed in a loop has been reached.")); "The maximum number of statements that can be executed in a loop has been reached."));
expected = expectThrows(PainlessError.class, () -> { expected = expectThrows(PainlessError.class, () -> {
exec("while (true) {int y = 5}"); exec("while (true) {int y = 5;}");
}); });
assertTrue(expected.getMessage().contains( assertTrue(expected.getMessage().contains(
"The maximum number of statements that can be executed in a loop has been reached.")); "The maximum number of statements that can be executed in a loop has been reached."));
@ -116,7 +116,7 @@ public class WhenThingsGoWrongTests extends ScriptTestCase {
"The maximum number of statements that can be executed in a loop has been reached.")); "The maximum number of statements that can be executed in a loop has been reached."));
expected = expectThrows(PainlessError.class, () -> { expected = expectThrows(PainlessError.class, () -> {
exec("for (;;) {int x = 5}"); exec("for (;;) {int x = 5;}");
fail("should have hit PainlessError"); fail("should have hit PainlessError");
}); });
assertTrue(expected.getMessage().contains( assertTrue(expected.getMessage().contains(
@ -130,7 +130,7 @@ public class WhenThingsGoWrongTests extends ScriptTestCase {
"The maximum number of statements that can be executed in a loop has been reached.")); "The maximum number of statements that can be executed in a loop has been reached."));
RuntimeException parseException = expectThrows(RuntimeException.class, () -> { RuntimeException parseException = expectThrows(RuntimeException.class, () -> {
exec("try { int x } catch (PainlessError error) {}"); exec("try { int x; } catch (PainlessError error) {}");
fail("should have hit ParseException"); fail("should have hit ParseException");
}); });
assertTrue(parseException.getMessage().contains("Not a type [PainlessError].")); assertTrue(parseException.getMessage().contains("Not a type [PainlessError]."));