From a6dee0fd3b1faddcda0effbb459602aecb05b25e Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 00:06:06 +0400 Subject: [PATCH 01/66] Initial project structure --- .idea/.name | 1 + .idea/compiler.xml | 23 + .idea/copyright/profiles_settings.xml | 5 + .idea/encodings.xml | 5 + .idea/kannotator.xml | 7 + .idea/libraries/KotlinJavaRuntime.xml | 11 + .../org_ow2_asm_asm_debug_all_4_1.xml | 15 + .idea/misc.xml | 10 + .idea/modules.xml | 9 + .idea/scopes/scope_settings.xml | 5 + .idea/vcs.xml | 8 + .../org/objectweb/asm/annotations.xml | 551 ++++++ .../org/objectweb/asm/commons/annotations.xml | 1037 ++++++++++++ .../objectweb/asm/signature/annotations.xml | 110 ++ .../asm/tree/analysis/annotations.xml | 590 +++++++ .../org/objectweb/asm/tree/annotations.xml | 545 ++++++ .../org/objectweb/asm/util/annotations.xml | 1496 +++++++++++++++++ .../org/objectweb/asm/xml/annotations.xml | 290 ++++ eval4j.iml | 14 + 19 files changed, 4732 insertions(+) create mode 100644 .idea/.name create mode 100644 .idea/compiler.xml create mode 100644 .idea/copyright/profiles_settings.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/kannotator.xml create mode 100644 .idea/libraries/KotlinJavaRuntime.xml create mode 100644 .idea/libraries/org_ow2_asm_asm_debug_all_4_1.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/scopes/scope_settings.xml create mode 100644 .idea/vcs.xml create mode 100644 annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/annotations.xml create mode 100644 annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/commons/annotations.xml create mode 100644 annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/signature/annotations.xml create mode 100644 annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/analysis/annotations.xml create mode 100644 annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml create mode 100644 annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/util/annotations.xml create mode 100644 annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/xml/annotations.xml create mode 100644 eval4j.iml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 00000000000..731b3d6f54e --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +eval4j \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 00000000000..217af471a9e --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 00000000000..3572571ad83 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 00000000000..e206d70d859 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/.idea/kannotator.xml b/.idea/kannotator.xml new file mode 100644 index 00000000000..48c712c3946 --- /dev/null +++ b/.idea/kannotator.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/.idea/libraries/KotlinJavaRuntime.xml b/.idea/libraries/KotlinJavaRuntime.xml new file mode 100644 index 00000000000..0b98ab4814d --- /dev/null +++ b/.idea/libraries/KotlinJavaRuntime.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/org_ow2_asm_asm_debug_all_4_1.xml b/.idea/libraries/org_ow2_asm_asm_debug_all_4_1.xml new file mode 100644 index 00000000000..7d9e29eb73c --- /dev/null +++ b/.idea/libraries/org_ow2_asm_asm_debug_all_4_1.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000000..97320410eec --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000000..313c2996166 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml new file mode 100644 index 00000000000..922003b8433 --- /dev/null +++ b/.idea/scopes/scope_settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000000..9d32e507a98 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/annotations.xml b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/annotations.xml new file mode 100644 index 00000000000..cba4afb9059 --- /dev/null +++ b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/annotations.xml @@ -0,0 +1,551 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/commons/annotations.xml b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/commons/annotations.xml new file mode 100644 index 00000000000..c7c2221a34f --- /dev/null +++ b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/commons/annotations.xml @@ -0,0 +1,1037 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/signature/annotations.xml b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/signature/annotations.xml new file mode 100644 index 00000000000..f53e24f7bcb --- /dev/null +++ b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/signature/annotations.xml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/analysis/annotations.xml b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/analysis/annotations.xml new file mode 100644 index 00000000000..ddcfa8e47c1 --- /dev/null +++ b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/analysis/annotations.xml @@ -0,0 +1,590 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml new file mode 100644 index 00000000000..30dfaa36edf --- /dev/null +++ b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml @@ -0,0 +1,545 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/util/annotations.xml b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/util/annotations.xml new file mode 100644 index 00000000000..18b031add3f --- /dev/null +++ b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/util/annotations.xml @@ -0,0 +1,1496 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/xml/annotations.xml b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/xml/annotations.xml new file mode 100644 index 00000000000..cad2755ff73 --- /dev/null +++ b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/xml/annotations.xml @@ -0,0 +1,290 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eval4j.iml b/eval4j.iml new file mode 100644 index 00000000000..ae1a999ff72 --- /dev/null +++ b/eval4j.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + From dae297d26280a2a4cce0442aa86f17f8178887c4 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 02:00:18 +0400 Subject: [PATCH 02/66] Nilary and unary operations supported --- src/org/jetbrains/eval4j/interpreter.kt | 284 ++++++++++++++++++++++++ src/org/jetbrains/eval4j/values.kt | 58 +++++ 2 files changed, 342 insertions(+) create mode 100644 src/org/jetbrains/eval4j/interpreter.kt create mode 100644 src/org/jetbrains/eval4j/values.kt diff --git a/src/org/jetbrains/eval4j/interpreter.kt b/src/org/jetbrains/eval4j/interpreter.kt new file mode 100644 index 00000000000..5e1ad387477 --- /dev/null +++ b/src/org/jetbrains/eval4j/interpreter.kt @@ -0,0 +1,284 @@ +package org.jetbrains.eval4j + +import org.objectweb.asm.tree.analysis.* +import org.objectweb.asm.Handle +import org.objectweb.asm.Opcodes.* +import org.objectweb.asm.Type +import org.objectweb.asm.tree.AbstractInsnNode +import org.objectweb.asm.tree.FieldInsnNode +import org.objectweb.asm.tree.IntInsnNode +import org.objectweb.asm.tree.InvokeDynamicInsnNode +import org.objectweb.asm.tree.LdcInsnNode +import org.objectweb.asm.tree.MethodInsnNode +import org.objectweb.asm.tree.MultiANewArrayInsnNode +import org.objectweb.asm.tree.TypeInsnNode +import org.objectweb.asm.tree.JumpInsnNode +import org.objectweb.asm.tree.IincInsnNode +import org.objectweb.asm.Label + +class UnsupportedByteCodeException(message: String) : RuntimeException(message) + +trait EvalStrategy { + fun loadClass(classType: Type): Value + fun newInstance(classType: Type): Value + fun checkCast(value: Value, targetType: Type): Value + fun isInsetanceOf(value: Value, targetType: Type): Boolean + + fun newArray(arrayType: Type, size: Int): Value + fun getArrayLength(array: Value): Value + + fun getStaticField(fieldDesc: String): Value + fun setStaticField(fieldDesc: String, newValue: Value) + + fun getField(instance: Value, fieldDesc: String): Value + fun setField(instance: Value, fieldDesc: String, newValue: Value) + + fun getLocalVariable(index: Int): Value + fun setLocalVariable(index: Int, newValue: Value) + + fun jump(label: Label) + + fun returnValue(value: Value) + fun throwException(value: Value) +} + +class EvalInterpreter(private val evalStrategy: EvalStrategy) : Interpreter(ASM4) { + override fun newValue(`type`: Type?): Value? { + if (`type` == null) { + return NOT_A_VALUE + } + + return when (`type`.getSort()) { + Type.VOID -> null + else -> NotInitialized(`type`) + } + } + + override fun newOperation(insn: AbstractInsnNode): Value? { + return when (insn.getOpcode()) { + ACONST_NULL -> { + return NULL_VALUE + } + + ICONST_M1 -> int(-1) + ICONST_0 -> int(0) + ICONST_1 -> int(1) + ICONST_2 -> int(2) + ICONST_3 -> int(3) + ICONST_4 -> int(4) + ICONST_5 -> int(5) + + LCONST_0 -> long(0) + LCONST_1 -> long(1) + + FCONST_0 -> float(0.0) + FCONST_1 -> float(1.0) + FCONST_2 -> float(2.0) + + DCONST_0 -> double(0.0) + DCONST_1 -> double(1.0) + + BIPUSH, SIPUSH -> int((insn as IntInsnNode).operand) + + LDC -> { + val cst = ((insn as LdcInsnNode)).cst + when (cst) { + is Int -> int(cst) + is Float -> float(cst) + is Long -> long(cst) + is Double -> double(cst) + is String -> obj(cst) + is Type -> { + val sort = (cst as Type).getSort() + when (sort) { + Type.OBJECT, Type.ARRAY -> evalStrategy.loadClass(cst) + Type.METHOD -> throw UnsupportedByteCodeException("Mothod handles are not supported") + else -> throw UnsupportedByteCodeException("Illegal LDC constant " + cst) + } + } + is Handle -> throw UnsupportedByteCodeException("Method handles are not supported") + else -> throw UnsupportedByteCodeException("Illegal LDC constant " + cst) + } + } + JSR -> LabelValue((insn as JumpInsnNode).label.getLabel()) + GETSTATIC -> evalStrategy.getStaticField((insn as FieldInsnNode).desc) + NEW -> evalStrategy.newInstance(Type.getType((insn as TypeInsnNode).desc)) + else -> throw UnsupportedByteCodeException("$insn") + } + } + + override fun copyOperation(insn: AbstractInsnNode, value: Value): Value { + return value + } + + override fun unaryOperation(insn: AbstractInsnNode, value: Value): Value? { + return when (insn.getOpcode()) { + INEG -> int(-value.int) + IINC -> { + val varIndex = (insn as IincInsnNode).`var` + evalStrategy.setLocalVariable( + varIndex, + int(evalStrategy.getLocalVariable(varIndex).int + insn.incr) + ) + + // TODO: don't know what to do here, returning what we got: + value + } + L2I -> int(value.long.toInt()) + F2I -> int(value.float.toInt()) + D2I -> int(value.double.toInt()) + I2B -> byte(value.int.toByte()) + I2C -> char(value.int.toChar()) + I2S -> short(value.int.toShort()) + + FNEG -> float(-value.float) + I2F -> float(value.int.toFloat()) + L2F -> float(value.long.toFloat()) + D2F -> float(value.double.toFloat()) + + LNEG -> long(-value.long) + I2L -> long(value.int.toLong()) + F2L -> long(value.float.toLong()) + D2L -> long(value.double.toLong()) + + DNEG -> double(-value.double) + I2D -> double(value.int.toDouble()) + L2D -> double(value.long.toDouble()) + F2D -> double(value.float.toDouble()) + + IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IFNULL, IFNONNULL -> { + val label = (insn as JumpInsnNode).label.getLabel() + when (insn.getOpcode()) { + IFEQ -> if (value.int == 0) evalStrategy.jump(label) + IFNE -> if (value.int != 0) evalStrategy.jump(label) + IFLT -> if (value.int < 0) evalStrategy.jump(label) + IFGT -> if (value.int > 0) evalStrategy.jump(label) + IFLE -> if (value.int <= 0) evalStrategy.jump(label) + IFGE -> if (value.int >= 0) evalStrategy.jump(label) + IFNULL -> if (value.obj == null) evalStrategy.jump(label) + IFNONNULL -> if (value.obj != null) evalStrategy.jump(label) + } + null + } + + // TODO: switch + TABLESWITCH, + LOOKUPSWITCH -> throw UnsupportedByteCodeException("Switch is not supported yet") + + IRETURN, + LRETURN, + FRETURN, + DRETURN, + ARETURN -> { + evalStrategy.returnValue(value) + null + } + + PUTSTATIC -> { + evalStrategy.setStaticField((insn as FieldInsnNode).desc, value) + null + } + + GETFIELD -> evalStrategy.getField(value, (insn as FieldInsnNode).desc) + + NEWARRAY -> { + val typeStr = when ((insn as IntInsnNode).operand) { + T_BOOLEAN -> "[Z" + T_CHAR -> "[C" + T_BYTE -> "[B" + T_SHORT -> "[S" + T_INT -> "[I" + T_FLOAT -> "[F" + T_DOUBLE -> "[D" + T_LONG -> "[J" + else -> throw AnalyzerException(insn, "Invalid array type") + } + evalStrategy.newArray(Type.getType(typeStr), value.int) + } + ANEWARRAY -> { + val desc = (insn as TypeInsnNode).desc + evalStrategy.newArray(Type.getType("[" + Type.getObjectType(desc)), value.int) + } + ARRAYLENGTH -> evalStrategy.getArrayLength(value) + + ATHROW -> { + evalStrategy.throwException(value) + null + } + + CHECKCAST -> { + val targetType = Type.getObjectType((insn as TypeInsnNode).desc) + evalStrategy.checkCast(value, targetType) + } + + INSTANCEOF -> { + val targetType = Type.getObjectType((insn as TypeInsnNode).desc) + boolean(evalStrategy.isInsetanceOf(value, targetType)) + } + + // TODO: maybe just do nothing? + MONITORENTER, MONITOREXIT -> throw UnsupportedByteCodeException("Monitor instructions are not supported") + + else -> throw UnsupportedByteCodeException("$insn") + } + } + + override fun binaryOperation(insn: AbstractInsnNode, value1: Value, value2: Value): Value { + when (insn.getOpcode()) { + IALOAD, BALOAD, CALOAD, SALOAD, IADD, ISUB, IMUL, IDIV, IREM, ISHL, ISHR, IUSHR, IAND, IOR, IXOR -> { + return Value.INT_VALUE + } + FALOAD, FADD, FSUB, FMUL, FDIV, FREM -> { + return Value.FLOAT_VALUE + } + LALOAD, LADD, LSUB, LMUL, LDIV, LREM, LSHL, LSHR, LUSHR, LAND, LOR, LXOR -> { + return Value.LONG_VALUE + } + DALOAD, DADD, DSUB, DMUL, DDIV, DREM -> { + return Value.DOUBLE_VALUE + } + AALOAD -> { + return Value.REFERENCE_VALUE + } + LCMP, FCMPL, FCMPG, DCMPL, DCMPG -> { + return Value.INT_VALUE + } + IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE, PUTFIELD -> { + return null + } + else -> { + throw Error("Internal error.") + } + } + } + + override fun ternaryOperation(insn: AbstractInsnNode, value1: Value, value2: Value?, value3: Value): Value? { + return null + } + + override fun naryOperation(insn: AbstractInsnNode, values: List): Value { + val opcode = insn.getOpcode()!! + if (opcode == MULTIANEWARRAY) { + return newValue(Type.getType(((insn as MultiANewArrayInsnNode)).desc)) + } + else if (opcode == INVOKEDYNAMIC) { + return newValue(Type.getReturnType(((insn as InvokeDynamicInsnNode)).desc)) + } + else { + return newValue(Type.getReturnType(((insn as MethodInsnNode)).desc)) + } + } + + + override fun returnOperation(insn: AbstractInsnNode?, value: Value, expected: Value?) { + + } + + override fun merge(v: Value, w: Value): Value { + if (!v.equals(w)) { + return Value.UNINITIALIZED_VALUE + } + + return v + } +} diff --git a/src/org/jetbrains/eval4j/values.kt b/src/org/jetbrains/eval4j/values.kt new file mode 100644 index 00000000000..06246a3967a --- /dev/null +++ b/src/org/jetbrains/eval4j/values.kt @@ -0,0 +1,58 @@ +package org.jetbrains.eval4j + +import org.objectweb.asm.Type +import org.objectweb.asm.Label + +trait Value : org.objectweb.asm.tree.analysis.Value { + val asmType: Type + val valid: Boolean + override fun getSize(): Int = asmType.getSize() +} + +abstract class AbstractValue( + override val asmType: Type +) : Value { + override val valid = true +} + +object NOT_A_VALUE: Value { + override val asmType = Type.getType("") + override val valid = false + override fun getSize(): Int = 1 +} + +class NotInitialized(override val asmType: Type): Value { + override val valid = false +} + +class IntValue(val value: Int, asmType: Type): AbstractValue(asmType) +class LongValue(val value: Long): AbstractValue(Type.LONG_TYPE) +class FloatValue(val value: Float): AbstractValue(Type.FLOAT_TYPE) +class DoubleValue(val value: Double): AbstractValue(Type.DOUBLE_TYPE) +class ObjectValue(val value: Any?, asmType: Type): AbstractValue(asmType) + +class LabelValue(val value: Label): AbstractValue(Type.VOID_TYPE) + +fun boolean(v: Boolean) = IntValue(if (v) 1 else 0, Type.BOOLEAN_TYPE) +fun byte(v: Byte) = IntValue(v.toInt(), Type.BYTE_TYPE) +fun short(v: Short) = IntValue(v.toInt(), Type.SHORT_TYPE) +fun char(v: Char) = IntValue(v.toInt(), Type.CHAR_TYPE) +fun int(v: Int) = IntValue(v, Type.INT_TYPE) +fun long(v: Long) = LongValue(v) +fun float(v: Float) = FloatValue(v) +fun double(v: Double) = DoubleValue(v) +fun obj(v: T, t: Type = if (v != null) Type.getType(v.javaClass) else Type.getType(javaClass())) = ObjectValue(v, t) + +val NULL_VALUE = ObjectValue(null, Type.getObjectType("null")) + +val Value.boolean: Boolean + get(): Boolean { + assert(this.asmType == Type.BOOLEAN_TYPE) + return (this as IntValue).value == 1 + } + +val Value.int: Int get() = (this as IntValue).value +val Value.long: Long get() = (this as LongValue).value +val Value.float: Float get() = (this as FloatValue).value +val Value.double: Double get() = (this as DoubleValue).value +val Value.obj: Any? get() = (this as ObjectValue).value \ No newline at end of file From fcc3fae8e57722e39ad43b14531d3aa75bc37b70 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 03:34:37 +0400 Subject: [PATCH 03/66] Interpreter completed --- .../asm/tree/analysis/annotations.xml | 843 ++++++++-------- .../org/objectweb/asm/tree/annotations.xml | 908 +++++++++--------- src/org/jetbrains/eval4j/interpreter.kt | 219 +++-- 3 files changed, 1047 insertions(+), 923 deletions(-) diff --git a/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/analysis/annotations.xml b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/analysis/annotations.xml index ddcfa8e47c1..582d9bf2dc6 100644 --- a/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/analysis/annotations.xml +++ b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/analysis/annotations.xml @@ -1,110 +1,23 @@ - + + + + - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - + - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -116,55 +29,19 @@ - - - - + + + + - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -173,7 +50,22 @@ - + + + + + + + + + + + + + + + + @@ -182,172 +74,28 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -359,64 +107,19 @@ - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -425,94 +128,190 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + + + + + + + - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - + - - - - - - - - - - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -524,29 +323,53 @@ - - - - - - - + - + - + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -554,22 +377,82 @@ - + - + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + @@ -578,10 +461,136 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml index 30dfaa36edf..d3a32fab38f 100644 --- a/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml +++ b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml @@ -1,158 +1,35 @@ + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -164,142 +41,52 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - + - - - - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -308,232 +95,334 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + + + + + + + + + + + + + + + + + + + + + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -542,4 +431,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/org/jetbrains/eval4j/interpreter.kt b/src/org/jetbrains/eval4j/interpreter.kt index 5e1ad387477..4d6d873c12c 100644 --- a/src/org/jetbrains/eval4j/interpreter.kt +++ b/src/org/jetbrains/eval4j/interpreter.kt @@ -7,7 +7,6 @@ import org.objectweb.asm.Type import org.objectweb.asm.tree.AbstractInsnNode import org.objectweb.asm.tree.FieldInsnNode import org.objectweb.asm.tree.IntInsnNode -import org.objectweb.asm.tree.InvokeDynamicInsnNode import org.objectweb.asm.tree.LdcInsnNode import org.objectweb.asm.tree.MethodInsnNode import org.objectweb.asm.tree.MultiANewArrayInsnNode @@ -25,13 +24,18 @@ trait EvalStrategy { fun isInsetanceOf(value: Value, targetType: Type): Boolean fun newArray(arrayType: Type, size: Int): Value + fun newMultiDimensionalArray(arrayType: Type, dimensionSizes: List): Value fun getArrayLength(array: Value): Value + fun getArrayElement(array: Value, index: Value): Value + fun setArrayElement(array: Value, index: Value, newValue: Value) fun getStaticField(fieldDesc: String): Value fun setStaticField(fieldDesc: String, newValue: Value) + fun invokeStaticMethod(methodDesc: String, arguments: List): Value fun getField(instance: Value, fieldDesc: String): Value fun setField(instance: Value, fieldDesc: String, newValue: Value) + fun invokeMethod(instance: Value, methodDesc: String, arguments: List, invokespecial: Boolean = false): Value fun getLocalVariable(index: Int): Value fun setLocalVariable(index: Int, newValue: Value) @@ -165,15 +169,6 @@ class EvalInterpreter(private val evalStrategy: EvalStrategy) : Interpreter throw UnsupportedByteCodeException("Switch is not supported yet") - IRETURN, - LRETURN, - FRETURN, - DRETURN, - ARETURN -> { - evalStrategy.returnValue(value) - null - } - PUTSTATIC -> { evalStrategy.setStaticField((insn as FieldInsnNode).desc, value) null @@ -223,62 +218,164 @@ class EvalInterpreter(private val evalStrategy: EvalStrategy) : Interpreter evalStrategy.getArrayElement(value1, value2) + + IADD -> int(value1.int + value2.int) + ISUB -> int(value1.int - value2.int) + IMUL -> int(value1.int * value2.int) + IDIV -> int(value1.int / value2.int) + IREM -> int(value1.int % value2.int) + ISHL -> int(value1.int shl value2.int) + ISHR -> int(value1.int shr value2.int) + IUSHR -> int(value1.int ushr value2.int) + IAND -> int(value1.int and value2.int) + IOR -> int(value1.int or value2.int) + IXOR -> int(value1.int xor value2.int) + + LADD -> long(value1.long + value2.long) + LSUB -> long(value1.long - value2.long) + LMUL -> long(value1.long * value2.long) + LDIV -> long(value1.long / value2.long) + LREM -> long(value1.long % value2.long) + LSHL -> long(value1.long shl value2.int) + LSHR -> long(value1.long shr value2.int) + LUSHR -> long(value1.long ushr value2.int) + LAND -> long(value1.long and value2.long) + LOR -> long(value1.long or value2.long) + LXOR -> long(value1.long xor value2.long) + + FADD -> float(value1.float + value2.float) + FSUB -> float(value1.float - value2.float) + FMUL -> float(value1.float * value2.float) + FDIV -> float(value1.float / value2.float) + FREM -> float(value1.float % value2.float) + + DADD -> double(value1.double + value2.double) + DSUB -> double(value1.double - value2.double) + DMUL -> double(value1.double * value2.double) + DDIV -> double(value1.double / value2.double) + DREM -> double(value1.double % value2.double) + + LCMP -> { + val l1 = value1.long + val l2 = value2.long + + int(when { + l1 > l2 -> 1 + l1 == l2 -> 0 + else -> -1 + }) + } + + FCMPL, + FCMPG -> { + val l1 = value1.float + val l2 = value2.float + + int(when { + l1 > l2 -> 1 + l1 == l2 -> 0 + l1 < l2 -> -1 + // one of them is NaN + else -> if (insn.getOpcode() == FCMPG) 1 else -1 + }) + } + + DCMPL, + DCMPG -> { + val l1 = value1.double + val l2 = value2.double + + int(when { + l1 > l2 -> 1 + l1 == l2 -> 0 + l1 < l2 -> -1 + // one of them is NaN + else -> if (insn.getOpcode() == DCMPG) 1 else -1 + }) + } + + IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE -> { + val label = (insn as JumpInsnNode).label.getLabel() + when (insn.getOpcode()) { + IF_ICMPEQ -> if (value1.int == value2.int) evalStrategy.jump(label) + IF_ICMPNE -> if (value1.int != value2.int) evalStrategy.jump(label) + IF_ICMPLT -> if (value1.int < value2.int) evalStrategy.jump(label) + IF_ICMPGT -> if (value1.int > value2.int) evalStrategy.jump(label) + IF_ICMPLE -> if (value1.int <= value2.int) evalStrategy.jump(label) + IF_ICMPGE -> if (value1.int >= value2.int) evalStrategy.jump(label) + + IF_ACMPEQ -> if (value1.obj == value2.obj) evalStrategy.jump(label) + IF_ACMPNE -> if (value1.obj != value2.obj) evalStrategy.jump(label) + } + null + } + + PUTFIELD -> { + evalStrategy.setField(value1, (insn as FieldInsnNode).desc, value2) + null + } + + else -> throw UnsupportedByteCodeException("$insn") + } + } + + override fun ternaryOperation(insn: AbstractInsnNode, value1: Value, value2: Value, value3: Value): Value? { + return when (insn.getOpcode()) { + IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE, CASTORE, SASTORE -> { + evalStrategy.setArrayElement(value1, value2, value3) + null + } + else -> throw UnsupportedByteCodeException("$insn") + } + } + + override fun naryOperation(insn: AbstractInsnNode, values: List): Value { + return when (insn.getOpcode()) { + MULTIANEWARRAY -> { + val node = insn as MultiANewArrayInsnNode + evalStrategy.newMultiDimensionalArray(Type.getType(node.desc), values.map { v -> v.int }) + } + + INVOKEVIRTUAL, INVOKESPECIAL, INVOKEINTERFACE -> { + val desc = (insn as MethodInsnNode).desc + evalStrategy.invokeMethod( + values[0], + desc, + values.subList(1, values.size()), + insn.getOpcode() == INVOKESPECIAL + ) + } + + INVOKESTATIC -> evalStrategy.invokeStaticMethod((insn as MethodInsnNode).desc, values) + + INVOKEDYNAMIC -> throw UnsupportedByteCodeException("INVOKEDYNAMIC is not supported") + else -> throw UnsupportedByteCodeException("$insn") + } + } + + + override fun returnOperation(insn: AbstractInsnNode, value: Value, expected: Value) { when (insn.getOpcode()) { - IALOAD, BALOAD, CALOAD, SALOAD, IADD, ISUB, IMUL, IDIV, IREM, ISHL, ISHR, IUSHR, IAND, IOR, IXOR -> { - return Value.INT_VALUE - } - FALOAD, FADD, FSUB, FMUL, FDIV, FREM -> { - return Value.FLOAT_VALUE - } - LALOAD, LADD, LSUB, LMUL, LDIV, LREM, LSHL, LSHR, LUSHR, LAND, LOR, LXOR -> { - return Value.LONG_VALUE - } - DALOAD, DADD, DSUB, DMUL, DDIV, DREM -> { - return Value.DOUBLE_VALUE - } - AALOAD -> { - return Value.REFERENCE_VALUE - } - LCMP, FCMPL, FCMPG, DCMPL, DCMPG -> { - return Value.INT_VALUE - } - IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE, PUTFIELD -> { - return null - } - else -> { - throw Error("Internal error.") + IRETURN, + LRETURN, + FRETURN, + DRETURN, + ARETURN -> { + // TODO: coercion, maybe? + evalStrategy.returnValue(value) } + + else -> throw UnsupportedByteCodeException("$insn") } } - override fun ternaryOperation(insn: AbstractInsnNode, value1: Value, value2: Value?, value3: Value): Value? { - return null - } - - override fun naryOperation(insn: AbstractInsnNode, values: List): Value { - val opcode = insn.getOpcode()!! - if (opcode == MULTIANEWARRAY) { - return newValue(Type.getType(((insn as MultiANewArrayInsnNode)).desc)) - } - else if (opcode == INVOKEDYNAMIC) { - return newValue(Type.getReturnType(((insn as InvokeDynamicInsnNode)).desc)) - } - else { - return newValue(Type.getReturnType(((insn as MethodInsnNode)).desc)) - } - } - - - override fun returnOperation(insn: AbstractInsnNode?, value: Value, expected: Value?) { - - } - override fun merge(v: Value, w: Value): Value { - if (!v.equals(w)) { - return Value.UNINITIALIZED_VALUE - } - - return v + // We always remember the NEW value + return w } } From a078727ea3e903d476881fb07a235d3774724afa Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 03:47:16 +0400 Subject: [PATCH 04/66] IINC treatment corrected --- src/org/jetbrains/eval4j/interpreter.kt | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/org/jetbrains/eval4j/interpreter.kt b/src/org/jetbrains/eval4j/interpreter.kt index 4d6d873c12c..42ebcc573b4 100644 --- a/src/org/jetbrains/eval4j/interpreter.kt +++ b/src/org/jetbrains/eval4j/interpreter.kt @@ -118,16 +118,7 @@ class EvalInterpreter(private val evalStrategy: EvalStrategy) : Interpreter int(-value.int) - IINC -> { - val varIndex = (insn as IincInsnNode).`var` - evalStrategy.setLocalVariable( - varIndex, - int(evalStrategy.getLocalVariable(varIndex).int + insn.incr) - ) - - // TODO: don't know what to do here, returning what we got: - value - } + IINC -> int(value.int + (insn as IincInsnNode).incr) L2I -> int(value.long.toInt()) F2I -> int(value.float.toInt()) D2I -> int(value.double.toInt()) From f1601b2da0485048c94249277238c3b7a5160fc8 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 03:52:09 +0400 Subject: [PATCH 05/66] Eval and Control separated --- src/org/jetbrains/eval4j/interpreter.kt | 74 +++++++++++++------------ 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/src/org/jetbrains/eval4j/interpreter.kt b/src/org/jetbrains/eval4j/interpreter.kt index 42ebcc573b4..fadaffee183 100644 --- a/src/org/jetbrains/eval4j/interpreter.kt +++ b/src/org/jetbrains/eval4j/interpreter.kt @@ -17,7 +17,7 @@ import org.objectweb.asm.Label class UnsupportedByteCodeException(message: String) : RuntimeException(message) -trait EvalStrategy { +trait Eval { fun loadClass(classType: Type): Value fun newInstance(classType: Type): Value fun checkCast(value: Value, targetType: Type): Value @@ -39,14 +39,16 @@ trait EvalStrategy { fun getLocalVariable(index: Int): Value fun setLocalVariable(index: Int, newValue: Value) +} +trait Control { fun jump(label: Label) fun returnValue(value: Value) fun throwException(value: Value) } -class EvalInterpreter(private val evalStrategy: EvalStrategy) : Interpreter(ASM4) { +class EvalInterpreter(private val eval: Eval, private val control: Control) : Interpreter(ASM4) { override fun newValue(`type`: Type?): Value? { if (`type` == null) { return NOT_A_VALUE @@ -95,7 +97,7 @@ class EvalInterpreter(private val evalStrategy: EvalStrategy) : Interpreter { val sort = (cst as Type).getSort() when (sort) { - Type.OBJECT, Type.ARRAY -> evalStrategy.loadClass(cst) + Type.OBJECT, Type.ARRAY -> eval.loadClass(cst) Type.METHOD -> throw UnsupportedByteCodeException("Mothod handles are not supported") else -> throw UnsupportedByteCodeException("Illegal LDC constant " + cst) } @@ -105,8 +107,8 @@ class EvalInterpreter(private val evalStrategy: EvalStrategy) : Interpreter LabelValue((insn as JumpInsnNode).label.getLabel()) - GETSTATIC -> evalStrategy.getStaticField((insn as FieldInsnNode).desc) - NEW -> evalStrategy.newInstance(Type.getType((insn as TypeInsnNode).desc)) + GETSTATIC -> eval.getStaticField((insn as FieldInsnNode).desc) + NEW -> eval.newInstance(Type.getType((insn as TypeInsnNode).desc)) else -> throw UnsupportedByteCodeException("$insn") } } @@ -144,14 +146,14 @@ class EvalInterpreter(private val evalStrategy: EvalStrategy) : Interpreter { val label = (insn as JumpInsnNode).label.getLabel() when (insn.getOpcode()) { - IFEQ -> if (value.int == 0) evalStrategy.jump(label) - IFNE -> if (value.int != 0) evalStrategy.jump(label) - IFLT -> if (value.int < 0) evalStrategy.jump(label) - IFGT -> if (value.int > 0) evalStrategy.jump(label) - IFLE -> if (value.int <= 0) evalStrategy.jump(label) - IFGE -> if (value.int >= 0) evalStrategy.jump(label) - IFNULL -> if (value.obj == null) evalStrategy.jump(label) - IFNONNULL -> if (value.obj != null) evalStrategy.jump(label) + IFEQ -> if (value.int == 0) control.jump(label) + IFNE -> if (value.int != 0) control.jump(label) + IFLT -> if (value.int < 0) control.jump(label) + IFGT -> if (value.int > 0) control.jump(label) + IFLE -> if (value.int <= 0) control.jump(label) + IFGE -> if (value.int >= 0) control.jump(label) + IFNULL -> if (value.obj == null) control.jump(label) + IFNONNULL -> if (value.obj != null) control.jump(label) } null } @@ -161,11 +163,11 @@ class EvalInterpreter(private val evalStrategy: EvalStrategy) : Interpreter throw UnsupportedByteCodeException("Switch is not supported yet") PUTSTATIC -> { - evalStrategy.setStaticField((insn as FieldInsnNode).desc, value) + eval.setStaticField((insn as FieldInsnNode).desc, value) null } - GETFIELD -> evalStrategy.getField(value, (insn as FieldInsnNode).desc) + GETFIELD -> eval.getField(value, (insn as FieldInsnNode).desc) NEWARRAY -> { val typeStr = when ((insn as IntInsnNode).operand) { @@ -179,27 +181,27 @@ class EvalInterpreter(private val evalStrategy: EvalStrategy) : Interpreter "[J" else -> throw AnalyzerException(insn, "Invalid array type") } - evalStrategy.newArray(Type.getType(typeStr), value.int) + eval.newArray(Type.getType(typeStr), value.int) } ANEWARRAY -> { val desc = (insn as TypeInsnNode).desc - evalStrategy.newArray(Type.getType("[" + Type.getObjectType(desc)), value.int) + eval.newArray(Type.getType("[" + Type.getObjectType(desc)), value.int) } - ARRAYLENGTH -> evalStrategy.getArrayLength(value) + ARRAYLENGTH -> eval.getArrayLength(value) ATHROW -> { - evalStrategy.throwException(value) + control.throwException(value) null } CHECKCAST -> { val targetType = Type.getObjectType((insn as TypeInsnNode).desc) - evalStrategy.checkCast(value, targetType) + eval.checkCast(value, targetType) } INSTANCEOF -> { val targetType = Type.getObjectType((insn as TypeInsnNode).desc) - boolean(evalStrategy.isInsetanceOf(value, targetType)) + boolean(eval.isInsetanceOf(value, targetType)) } // TODO: maybe just do nothing? @@ -213,7 +215,7 @@ class EvalInterpreter(private val evalStrategy: EvalStrategy) : Interpreter evalStrategy.getArrayElement(value1, value2) + AALOAD -> eval.getArrayElement(value1, value2) IADD -> int(value1.int + value2.int) ISUB -> int(value1.int - value2.int) @@ -293,21 +295,21 @@ class EvalInterpreter(private val evalStrategy: EvalStrategy) : Interpreter { val label = (insn as JumpInsnNode).label.getLabel() when (insn.getOpcode()) { - IF_ICMPEQ -> if (value1.int == value2.int) evalStrategy.jump(label) - IF_ICMPNE -> if (value1.int != value2.int) evalStrategy.jump(label) - IF_ICMPLT -> if (value1.int < value2.int) evalStrategy.jump(label) - IF_ICMPGT -> if (value1.int > value2.int) evalStrategy.jump(label) - IF_ICMPLE -> if (value1.int <= value2.int) evalStrategy.jump(label) - IF_ICMPGE -> if (value1.int >= value2.int) evalStrategy.jump(label) + IF_ICMPEQ -> if (value1.int == value2.int) control.jump(label) + IF_ICMPNE -> if (value1.int != value2.int) control.jump(label) + IF_ICMPLT -> if (value1.int < value2.int) control.jump(label) + IF_ICMPGT -> if (value1.int > value2.int) control.jump(label) + IF_ICMPLE -> if (value1.int <= value2.int) control.jump(label) + IF_ICMPGE -> if (value1.int >= value2.int) control.jump(label) - IF_ACMPEQ -> if (value1.obj == value2.obj) evalStrategy.jump(label) - IF_ACMPNE -> if (value1.obj != value2.obj) evalStrategy.jump(label) + IF_ACMPEQ -> if (value1.obj == value2.obj) control.jump(label) + IF_ACMPNE -> if (value1.obj != value2.obj) control.jump(label) } null } PUTFIELD -> { - evalStrategy.setField(value1, (insn as FieldInsnNode).desc, value2) + eval.setField(value1, (insn as FieldInsnNode).desc, value2) null } @@ -318,7 +320,7 @@ class EvalInterpreter(private val evalStrategy: EvalStrategy) : Interpreter { - evalStrategy.setArrayElement(value1, value2, value3) + eval.setArrayElement(value1, value2, value3) null } else -> throw UnsupportedByteCodeException("$insn") @@ -329,12 +331,12 @@ class EvalInterpreter(private val evalStrategy: EvalStrategy) : Interpreter { val node = insn as MultiANewArrayInsnNode - evalStrategy.newMultiDimensionalArray(Type.getType(node.desc), values.map { v -> v.int }) + eval.newMultiDimensionalArray(Type.getType(node.desc), values.map { v -> v.int }) } INVOKEVIRTUAL, INVOKESPECIAL, INVOKEINTERFACE -> { val desc = (insn as MethodInsnNode).desc - evalStrategy.invokeMethod( + eval.invokeMethod( values[0], desc, values.subList(1, values.size()), @@ -342,7 +344,7 @@ class EvalInterpreter(private val evalStrategy: EvalStrategy) : Interpreter evalStrategy.invokeStaticMethod((insn as MethodInsnNode).desc, values) + INVOKESTATIC -> eval.invokeStaticMethod((insn as MethodInsnNode).desc, values) INVOKEDYNAMIC -> throw UnsupportedByteCodeException("INVOKEDYNAMIC is not supported") else -> throw UnsupportedByteCodeException("$insn") @@ -358,7 +360,7 @@ class EvalInterpreter(private val evalStrategy: EvalStrategy) : Interpreter { // TODO: coercion, maybe? - evalStrategy.returnValue(value) + control.returnValue(value) } else -> throw UnsupportedByteCodeException("$insn") From 2a24c0053916b24213bd536bbdc386977acf4dae Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 12:25:21 +0400 Subject: [PATCH 06/66] Unused methods removed --- src/org/jetbrains/eval4j/interpreter.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/org/jetbrains/eval4j/interpreter.kt b/src/org/jetbrains/eval4j/interpreter.kt index fadaffee183..9e3b55ccf50 100644 --- a/src/org/jetbrains/eval4j/interpreter.kt +++ b/src/org/jetbrains/eval4j/interpreter.kt @@ -36,9 +36,6 @@ trait Eval { fun getField(instance: Value, fieldDesc: String): Value fun setField(instance: Value, fieldDesc: String, newValue: Value) fun invokeMethod(instance: Value, methodDesc: String, arguments: List, invokespecial: Boolean = false): Value - - fun getLocalVariable(index: Int): Value - fun setLocalVariable(index: Int, newValue: Value) } trait Control { From 8aad18e8491b362e18ea484710a7829275800100 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 12:31:15 +0400 Subject: [PATCH 07/66] Class renamed --- src/org/jetbrains/eval4j/interpreter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/jetbrains/eval4j/interpreter.kt b/src/org/jetbrains/eval4j/interpreter.kt index 9e3b55ccf50..a1dabe25e55 100644 --- a/src/org/jetbrains/eval4j/interpreter.kt +++ b/src/org/jetbrains/eval4j/interpreter.kt @@ -45,7 +45,7 @@ trait Control { fun throwException(value: Value) } -class EvalInterpreter(private val eval: Eval, private val control: Control) : Interpreter(ASM4) { +class SingleInstructionInterpreter(private val eval: Eval, private val control: Control) : Interpreter(ASM4) { override fun newValue(`type`: Type?): Value? { if (`type` == null) { return NOT_A_VALUE From 21e0dabf874129a8e24d52685c5124e413739dd2 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 12:32:38 +0400 Subject: [PATCH 08/66] Using LabelNode instead of bare Label --- src/org/jetbrains/eval4j/interpreter.kt | 10 +++++----- src/org/jetbrains/eval4j/values.kt | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/org/jetbrains/eval4j/interpreter.kt b/src/org/jetbrains/eval4j/interpreter.kt index a1dabe25e55..122a627dd7e 100644 --- a/src/org/jetbrains/eval4j/interpreter.kt +++ b/src/org/jetbrains/eval4j/interpreter.kt @@ -13,7 +13,7 @@ import org.objectweb.asm.tree.MultiANewArrayInsnNode import org.objectweb.asm.tree.TypeInsnNode import org.objectweb.asm.tree.JumpInsnNode import org.objectweb.asm.tree.IincInsnNode -import org.objectweb.asm.Label +import org.objectweb.asm.tree.LabelNode class UnsupportedByteCodeException(message: String) : RuntimeException(message) @@ -39,7 +39,7 @@ trait Eval { } trait Control { - fun jump(label: Label) + fun jump(label: LabelNode) fun returnValue(value: Value) fun throwException(value: Value) @@ -103,7 +103,7 @@ class SingleInstructionInterpreter(private val eval: Eval, private val control: else -> throw UnsupportedByteCodeException("Illegal LDC constant " + cst) } } - JSR -> LabelValue((insn as JumpInsnNode).label.getLabel()) + JSR -> LabelValue((insn as JumpInsnNode).label) GETSTATIC -> eval.getStaticField((insn as FieldInsnNode).desc) NEW -> eval.newInstance(Type.getType((insn as TypeInsnNode).desc)) else -> throw UnsupportedByteCodeException("$insn") @@ -141,7 +141,7 @@ class SingleInstructionInterpreter(private val eval: Eval, private val control: F2D -> double(value.float.toDouble()) IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IFNULL, IFNONNULL -> { - val label = (insn as JumpInsnNode).label.getLabel() + val label = (insn as JumpInsnNode).label when (insn.getOpcode()) { IFEQ -> if (value.int == 0) control.jump(label) IFNE -> if (value.int != 0) control.jump(label) @@ -290,7 +290,7 @@ class SingleInstructionInterpreter(private val eval: Eval, private val control: } IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE -> { - val label = (insn as JumpInsnNode).label.getLabel() + val label = (insn as JumpInsnNode).label when (insn.getOpcode()) { IF_ICMPEQ -> if (value1.int == value2.int) control.jump(label) IF_ICMPNE -> if (value1.int != value2.int) control.jump(label) diff --git a/src/org/jetbrains/eval4j/values.kt b/src/org/jetbrains/eval4j/values.kt index 06246a3967a..94dc363aca5 100644 --- a/src/org/jetbrains/eval4j/values.kt +++ b/src/org/jetbrains/eval4j/values.kt @@ -1,7 +1,7 @@ package org.jetbrains.eval4j import org.objectweb.asm.Type -import org.objectweb.asm.Label +import org.objectweb.asm.tree.LabelNode trait Value : org.objectweb.asm.tree.analysis.Value { val asmType: Type @@ -31,7 +31,7 @@ class FloatValue(val value: Float): AbstractValue(Type.FLOAT_TYPE) class DoubleValue(val value: Double): AbstractValue(Type.DOUBLE_TYPE) class ObjectValue(val value: Any?, asmType: Type): AbstractValue(asmType) -class LabelValue(val value: Label): AbstractValue(Type.VOID_TYPE) +class LabelValue(val value: LabelNode): AbstractValue(Type.VOID_TYPE) fun boolean(v: Boolean) = IntValue(if (v) 1 else 0, Type.BOOLEAN_TYPE) fun byte(v: Byte) = IntValue(v.toInt(), Type.BYTE_TYPE) From f93d209b8811f14f545348d4ddf3863a7cdc30ac Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 13:22:16 +0400 Subject: [PATCH 09/66] Control logic will be handled by interpreter loop --- src/org/jetbrains/eval4j/interpreter.kt | 67 +++++++++++++------------ 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/src/org/jetbrains/eval4j/interpreter.kt b/src/org/jetbrains/eval4j/interpreter.kt index 122a627dd7e..05ebb8e6100 100644 --- a/src/org/jetbrains/eval4j/interpreter.kt +++ b/src/org/jetbrains/eval4j/interpreter.kt @@ -38,14 +38,7 @@ trait Eval { fun invokeMethod(instance: Value, methodDesc: String, arguments: List, invokespecial: Boolean = false): Value } -trait Control { - fun jump(label: LabelNode) - - fun returnValue(value: Value) - fun throwException(value: Value) -} - -class SingleInstructionInterpreter(private val eval: Eval, private val control: Control) : Interpreter(ASM4) { +class SingleInstructionInterpreter(private val eval: Eval) : Interpreter(ASM4) { override fun newValue(`type`: Type?): Value? { if (`type` == null) { return NOT_A_VALUE @@ -141,17 +134,7 @@ class SingleInstructionInterpreter(private val eval: Eval, private val control: F2D -> double(value.float.toDouble()) IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IFNULL, IFNONNULL -> { - val label = (insn as JumpInsnNode).label - when (insn.getOpcode()) { - IFEQ -> if (value.int == 0) control.jump(label) - IFNE -> if (value.int != 0) control.jump(label) - IFLT -> if (value.int < 0) control.jump(label) - IFGT -> if (value.int > 0) control.jump(label) - IFLE -> if (value.int <= 0) control.jump(label) - IFGE -> if (value.int >= 0) control.jump(label) - IFNULL -> if (value.obj == null) control.jump(label) - IFNONNULL -> if (value.obj != null) control.jump(label) - } + // Handled by interpreter loop, see checkUnaryCondition() null } @@ -187,7 +170,7 @@ class SingleInstructionInterpreter(private val eval: Eval, private val control: ARRAYLENGTH -> eval.getArrayLength(value) ATHROW -> { - control.throwException(value) + // Handled by interpreter loop null } @@ -208,6 +191,20 @@ class SingleInstructionInterpreter(private val eval: Eval, private val control: } } + public fun checkUnaryCondition(value: Value, opcode: Int): Boolean { + return when (opcode) { + IFEQ -> value.int == 0 + IFNE -> value.int != 0 + IFLT -> value.int < 0 + IFGT -> value.int > 0 + IFLE -> value.int <= 0 + IFGE -> value.int >= 0 + IFNULL -> value.obj == null + IFNONNULL -> value.obj != null + else -> throw UnsupportedByteCodeException("Unknown opcode: $opcode") + } + } + override fun binaryOperation(insn: AbstractInsnNode, value1: Value, value2: Value): Value? { return when (insn.getOpcode()) { IALOAD, BALOAD, CALOAD, SALOAD, @@ -290,18 +287,7 @@ class SingleInstructionInterpreter(private val eval: Eval, private val control: } IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE -> { - val label = (insn as JumpInsnNode).label - when (insn.getOpcode()) { - IF_ICMPEQ -> if (value1.int == value2.int) control.jump(label) - IF_ICMPNE -> if (value1.int != value2.int) control.jump(label) - IF_ICMPLT -> if (value1.int < value2.int) control.jump(label) - IF_ICMPGT -> if (value1.int > value2.int) control.jump(label) - IF_ICMPLE -> if (value1.int <= value2.int) control.jump(label) - IF_ICMPGE -> if (value1.int >= value2.int) control.jump(label) - - IF_ACMPEQ -> if (value1.obj == value2.obj) control.jump(label) - IF_ACMPNE -> if (value1.obj != value2.obj) control.jump(label) - } + // Handled by interpreter loop, see checkBinaryCondition() null } @@ -314,6 +300,21 @@ class SingleInstructionInterpreter(private val eval: Eval, private val control: } } + public fun checkBinaryCondition(value1: Value, value2: Value, opcode: Int): Boolean { + return when (opcode) { + IF_ICMPEQ -> value1.int == value2.int + IF_ICMPNE -> value1.int != value2.int + IF_ICMPLT -> value1.int < value2.int + IF_ICMPGT -> value1.int > value2.int + IF_ICMPLE -> value1.int <= value2.int + IF_ICMPGE -> value1.int >= value2.int + + IF_ACMPEQ -> value1.obj == value2.obj + IF_ACMPNE -> value1.obj != value2.obj + else -> throw UnsupportedByteCodeException("Unknown opcode: $opcode") + } + } + override fun ternaryOperation(insn: AbstractInsnNode, value1: Value, value2: Value, value3: Value): Value? { return when (insn.getOpcode()) { IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE, CASTORE, SASTORE -> { @@ -357,7 +358,7 @@ class SingleInstructionInterpreter(private val eval: Eval, private val control: DRETURN, ARETURN -> { // TODO: coercion, maybe? - control.returnValue(value) + // Handled by interpreter loop } else -> throw UnsupportedByteCodeException("$insn") From 2e374df5a9fd0dea579c9dc5c3f1cb5829ee2a52 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 13:23:24 +0400 Subject: [PATCH 10/66] Formatting --- src/org/jetbrains/eval4j/interpreter.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/org/jetbrains/eval4j/interpreter.kt b/src/org/jetbrains/eval4j/interpreter.kt index 05ebb8e6100..6afb6f85535 100644 --- a/src/org/jetbrains/eval4j/interpreter.kt +++ b/src/org/jetbrains/eval4j/interpreter.kt @@ -352,11 +352,7 @@ class SingleInstructionInterpreter(private val eval: Eval) : Interpreter( override fun returnOperation(insn: AbstractInsnNode, value: Value, expected: Value) { when (insn.getOpcode()) { - IRETURN, - LRETURN, - FRETURN, - DRETURN, - ARETURN -> { + IRETURN, LRETURN, FRETURN, DRETURN, ARETURN -> { // TODO: coercion, maybe? // Handled by interpreter loop } From 7559c3bd94e28a11da0a1f016173a635013742d1 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 13:33:15 +0400 Subject: [PATCH 11/66] Main interpreter loop implemented --- .../org/objectweb/asm/tree/annotations.xml | 6 + src/org/jetbrains/eval4j/interpreterLoop.kt | 139 ++++++++++++++++++ src/org/jetbrains/eval4j/values.kt | 5 + 3 files changed, 150 insertions(+) create mode 100644 src/org/jetbrains/eval4j/interpreterLoop.kt diff --git a/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml index d3a32fab38f..8e4c3eca809 100644 --- a/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml +++ b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml @@ -341,6 +341,12 @@ + + + + + + diff --git a/src/org/jetbrains/eval4j/interpreterLoop.kt b/src/org/jetbrains/eval4j/interpreterLoop.kt new file mode 100644 index 00000000000..934e1220f89 --- /dev/null +++ b/src/org/jetbrains/eval4j/interpreterLoop.kt @@ -0,0 +1,139 @@ +package org.jetbrains.eval4j + +import org.objectweb.asm.tree.LabelNode +import org.objectweb.asm.tree.AbstractInsnNode +import org.objectweb.asm.tree.analysis.Frame +import org.objectweb.asm.tree.MethodNode +import org.objectweb.asm.Type +import org.objectweb.asm.Opcodes.* +import org.objectweb.asm.tree.analysis.Interpreter +import org.objectweb.asm.tree.JumpInsnNode +import org.objectweb.asm.tree.VarInsnNode + +trait InterpreterResult +object NOTHING_DONE: InterpreterResult +class ExceptionThrown(val exception: Value): InterpreterResult +class ValueReturned(val result: Value): InterpreterResult +class AbnormalTermination(val message: String): InterpreterResult + +trait InterpretationEventHandler { + // If a non-null value is returned, interpreter loop is terminated and that value is used as a result + fun instructionProcessed(insn: AbstractInsnNode): InterpreterResult? + + fun exceptionThrown(currentState: Frame, currentInsn: AbstractInsnNode, exception: Value): InterpreterResult? + fun exceptionCaught(currentState: Frame, currentInsn: AbstractInsnNode, exception: Value): InterpreterResult? +} + +fun interpreterLoop( + ownerClassInternalName: String, + m: MethodNode, + eval: Eval, + instructionHandler: (AbstractInsnNode) -> InterpreterResult? +): InterpreterResult { + val firstInsn = m.instructions.getFirst() + if (firstInsn == null) return NOTHING_DONE + + var currentInsn = firstInsn!! + + fun goto(nextInsn: AbstractInsnNode?) { + if (nextInsn == null) throw IllegalArgumentException("Instruction flow ended with no RETURN") + currentInsn = nextInsn + } + + val interpreter = SingleInstructionInterpreter(eval) + try { + var frame = initFrame(ownerClassInternalName, m, interpreter) + + while (true) { + // TODO try-catch-finally support + // TODO support exceptions thrown by eval + + val insnOpcode = currentInsn.getOpcode() + val insnType = currentInsn.getType() + + when (insnType) { + AbstractInsnNode.LABEL, + AbstractInsnNode.FRAME, + AbstractInsnNode.LINE -> { + // skip to the next instruction + } + + else -> when (insnOpcode) { + GOTO -> { + goto((currentInsn as JumpInsnNode).label) + continue + } + + RET -> { + val varNode = currentInsn as VarInsnNode + val address = frame.getLocal(varNode.`var`) + goto((address as LabelValue).value) + continue + } + + // TODO: switch + LOOKUPSWITCH -> UnsupportedByteCodeException("LOOKUPSWITCH is not supported yet") + TABLESWITCH -> UnsupportedByteCodeException("TABLESWITCH is not supported yet") + + IRETURN, LRETURN, FRETURN, DRETURN, ARETURN -> return ValueReturned(frame.getStack(0)!!) + RETURN -> return ValueReturned(VOID_VALUE) + IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IFNULL, IFNONNULL -> { + if (interpreter.checkUnaryCondition(frame.getStack(0)!!, insnOpcode)) { + goto((currentInsn as JumpInsnNode).label) + continue + } + } + IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE -> { + if (interpreter.checkBinaryCondition(frame.getStack(0)!!, frame.getStack(1)!!, insnOpcode)) { + goto((currentInsn as JumpInsnNode).label) + continue + } + } + + // TODO: try/catch/finally + ATHROW -> return ExceptionThrown(frame.getStack(0)!!) + } + } + + frame.execute(currentInsn, interpreter) + + val handled = instructionHandler(currentInsn) + if (handled != null) return handled + + goto(currentInsn.getNext()) + } + } + catch (e: Throwable) { + throw e + } +} + +// Copied from org.objectweb.asm.tree.analysis.Analyzer.analyze() +fun initFrame( + owner: String, + m: MethodNode, + interpreter: Interpreter +): Frame { + val current = Frame(m.maxLocals, m.maxStack) + current.setReturn(interpreter.newValue(Type.getReturnType(m.desc))) + + var local = 0 + if ((m.access and ACC_STATIC) == 0) { + val ctype = Type.getObjectType(owner) + current.setLocal(local++, interpreter.newValue(ctype)) + } + + val args = Type.getArgumentTypes(m.desc) + for (i in 0..args.size - 1) { + current.setLocal(local++, interpreter.newValue(args[i])) + if (args[i].getSize() == 2) { + current.setLocal(local++, interpreter.newValue(null)) + } + } + + while (local < m.maxLocals) { + current.setLocal(local++, interpreter.newValue(null)) + } + + return current +} \ No newline at end of file diff --git a/src/org/jetbrains/eval4j/values.kt b/src/org/jetbrains/eval4j/values.kt index 94dc363aca5..80ac78f9e81 100644 --- a/src/org/jetbrains/eval4j/values.kt +++ b/src/org/jetbrains/eval4j/values.kt @@ -21,6 +21,11 @@ object NOT_A_VALUE: Value { override fun getSize(): Int = 1 } +object VOID_VALUE: Value { + override val asmType: Type = Type.VOID_TYPE + override val valid: Boolean = false +} + class NotInitialized(override val asmType: Type): Value { override val valid = false } From 3bae3e25b5bacf30787b89c24fa3476cb4a01b55 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 13:35:46 +0400 Subject: [PATCH 12/66] Support for throwing exceptions from Eval --- src/org/jetbrains/eval4j/interpreterLoop.kt | 102 ++++++++++---------- 1 file changed, 52 insertions(+), 50 deletions(-) diff --git a/src/org/jetbrains/eval4j/interpreterLoop.kt b/src/org/jetbrains/eval4j/interpreterLoop.kt index 934e1220f89..6dd426a1be2 100644 --- a/src/org/jetbrains/eval4j/interpreterLoop.kt +++ b/src/org/jetbrains/eval4j/interpreterLoop.kt @@ -24,6 +24,8 @@ trait InterpretationEventHandler { fun exceptionCaught(currentState: Frame, currentInsn: AbstractInsnNode, exception: Value): InterpreterResult? } +class ThrownFromEvalException(val exception: Value): RuntimeException() + fun interpreterLoop( ownerClassInternalName: String, m: MethodNode, @@ -41,70 +43,70 @@ fun interpreterLoop( } val interpreter = SingleInstructionInterpreter(eval) - try { var frame = initFrame(ownerClassInternalName, m, interpreter) - while (true) { - // TODO try-catch-finally support - // TODO support exceptions thrown by eval + while (true) { + // TODO try-catch-finally support - val insnOpcode = currentInsn.getOpcode() - val insnType = currentInsn.getType() + val insnOpcode = currentInsn.getOpcode() + val insnType = currentInsn.getType() - when (insnType) { - AbstractInsnNode.LABEL, - AbstractInsnNode.FRAME, - AbstractInsnNode.LINE -> { - // skip to the next instruction + when (insnType) { + AbstractInsnNode.LABEL, + AbstractInsnNode.FRAME, + AbstractInsnNode.LINE -> { + // skip to the next instruction + } + + else -> when (insnOpcode) { + GOTO -> { + goto((currentInsn as JumpInsnNode).label) + continue } - else -> when (insnOpcode) { - GOTO -> { + RET -> { + val varNode = currentInsn as VarInsnNode + val address = frame.getLocal(varNode.`var`) + goto((address as LabelValue).value) + continue + } + + // TODO: switch + LOOKUPSWITCH -> UnsupportedByteCodeException("LOOKUPSWITCH is not supported yet") + TABLESWITCH -> UnsupportedByteCodeException("TABLESWITCH is not supported yet") + + IRETURN, LRETURN, FRETURN, DRETURN, ARETURN -> return ValueReturned(frame.getStack(0)!!) + RETURN -> return ValueReturned(VOID_VALUE) + IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IFNULL, IFNONNULL -> { + if (interpreter.checkUnaryCondition(frame.getStack(0)!!, insnOpcode)) { goto((currentInsn as JumpInsnNode).label) continue } - - RET -> { - val varNode = currentInsn as VarInsnNode - val address = frame.getLocal(varNode.`var`) - goto((address as LabelValue).value) + } + IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE -> { + if (interpreter.checkBinaryCondition(frame.getStack(0)!!, frame.getStack(1)!!, insnOpcode)) { + goto((currentInsn as JumpInsnNode).label) continue } - - // TODO: switch - LOOKUPSWITCH -> UnsupportedByteCodeException("LOOKUPSWITCH is not supported yet") - TABLESWITCH -> UnsupportedByteCodeException("TABLESWITCH is not supported yet") - - IRETURN, LRETURN, FRETURN, DRETURN, ARETURN -> return ValueReturned(frame.getStack(0)!!) - RETURN -> return ValueReturned(VOID_VALUE) - IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IFNULL, IFNONNULL -> { - if (interpreter.checkUnaryCondition(frame.getStack(0)!!, insnOpcode)) { - goto((currentInsn as JumpInsnNode).label) - continue - } - } - IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE -> { - if (interpreter.checkBinaryCondition(frame.getStack(0)!!, frame.getStack(1)!!, insnOpcode)) { - goto((currentInsn as JumpInsnNode).label) - continue - } - } - - // TODO: try/catch/finally - ATHROW -> return ExceptionThrown(frame.getStack(0)!!) } + + // TODO: try/catch/finally + ATHROW -> return ExceptionThrown(frame.getStack(0)!!) } - - frame.execute(currentInsn, interpreter) - - val handled = instructionHandler(currentInsn) - if (handled != null) return handled - - goto(currentInsn.getNext()) } - } - catch (e: Throwable) { - throw e + + try { + frame.execute(currentInsn, interpreter) + } + catch (e: ThrownFromEvalException) { + // TODO: try/catch.finaly + return ExceptionThrown(e.exception) + } + + val handled = instructionHandler(currentInsn) + if (handled != null) return handled + + goto(currentInsn.getNext()) } } From b926e79aacc5d86af3471860551e2c3c108970c9 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 13:40:20 +0400 Subject: [PATCH 13/66] Support for interpreter events --- src/org/jetbrains/eval4j/interpreterLoop.kt | 22 ++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/org/jetbrains/eval4j/interpreterLoop.kt b/src/org/jetbrains/eval4j/interpreterLoop.kt index 6dd426a1be2..01211c0b020 100644 --- a/src/org/jetbrains/eval4j/interpreterLoop.kt +++ b/src/org/jetbrains/eval4j/interpreterLoop.kt @@ -17,6 +17,15 @@ class ValueReturned(val result: Value): InterpreterResult class AbnormalTermination(val message: String): InterpreterResult trait InterpretationEventHandler { + + class object { + object NONE : InterpretationEventHandler { + override fun instructionProcessed(insn: AbstractInsnNode): InterpreterResult? = null + override fun exceptionThrown(currentState: Frame, currentInsn: AbstractInsnNode, exception: Value): InterpreterResult? = null + override fun exceptionCaught(currentState: Frame, currentInsn: AbstractInsnNode, exception: Value): InterpreterResult? = null + } + } + // If a non-null value is returned, interpreter loop is terminated and that value is used as a result fun instructionProcessed(insn: AbstractInsnNode): InterpreterResult? @@ -30,7 +39,7 @@ fun interpreterLoop( ownerClassInternalName: String, m: MethodNode, eval: Eval, - instructionHandler: (AbstractInsnNode) -> InterpreterResult? + handler: InterpretationEventHandler = InterpretationEventHandler.NONE ): InterpreterResult { val firstInsn = m.instructions.getFirst() if (firstInsn == null) return NOTHING_DONE @@ -91,7 +100,12 @@ fun interpreterLoop( } // TODO: try/catch/finally - ATHROW -> return ExceptionThrown(frame.getStack(0)!!) + ATHROW -> { + val exceptionValue = frame.getStack(0)!! + val handled = handler.exceptionThrown(frame, currentInsn, exceptionValue) + if (handled != null) return handled + return ExceptionThrown(exceptionValue) + } } } @@ -100,10 +114,12 @@ fun interpreterLoop( } catch (e: ThrownFromEvalException) { // TODO: try/catch.finaly + val handled = handler.exceptionThrown(frame, currentInsn, e.exception) + if (handled != null) return handled return ExceptionThrown(e.exception) } - val handled = instructionHandler(currentInsn) + val handled = handler.instructionProcessed(currentInsn) if (handled != null) return handled goto(currentInsn.getNext()) From 0aca17ce15cad1ec228067e50ed074e1d04d4979 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 13:52:39 +0400 Subject: [PATCH 14/66] Do not evaluate label instructions and such --- src/org/jetbrains/eval4j/interpreterLoop.kt | 85 +++++++++++---------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/src/org/jetbrains/eval4j/interpreterLoop.kt b/src/org/jetbrains/eval4j/interpreterLoop.kt index 01211c0b020..06105bbf430 100644 --- a/src/org/jetbrains/eval4j/interpreterLoop.kt +++ b/src/org/jetbrains/eval4j/interpreterLoop.kt @@ -67,58 +67,63 @@ fun interpreterLoop( // skip to the next instruction } - else -> when (insnOpcode) { - GOTO -> { - goto((currentInsn as JumpInsnNode).label) - continue - } - - RET -> { - val varNode = currentInsn as VarInsnNode - val address = frame.getLocal(varNode.`var`) - goto((address as LabelValue).value) - continue - } - - // TODO: switch - LOOKUPSWITCH -> UnsupportedByteCodeException("LOOKUPSWITCH is not supported yet") - TABLESWITCH -> UnsupportedByteCodeException("TABLESWITCH is not supported yet") - - IRETURN, LRETURN, FRETURN, DRETURN, ARETURN -> return ValueReturned(frame.getStack(0)!!) - RETURN -> return ValueReturned(VOID_VALUE) - IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IFNULL, IFNONNULL -> { - if (interpreter.checkUnaryCondition(frame.getStack(0)!!, insnOpcode)) { + else -> { + when (insnOpcode) { + GOTO -> { goto((currentInsn as JumpInsnNode).label) continue } - } - IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE -> { - if (interpreter.checkBinaryCondition(frame.getStack(0)!!, frame.getStack(1)!!, insnOpcode)) { - goto((currentInsn as JumpInsnNode).label) + + RET -> { + val varNode = currentInsn as VarInsnNode + val address = frame.getLocal(varNode.`var`) + goto((address as LabelValue).value) continue } + + // TODO: switch + LOOKUPSWITCH -> UnsupportedByteCodeException("LOOKUPSWITCH is not supported yet") + TABLESWITCH -> UnsupportedByteCodeException("TABLESWITCH is not supported yet") + + IRETURN, LRETURN, FRETURN, DRETURN, ARETURN -> return ValueReturned(frame.getStack(0)!!) + RETURN -> return ValueReturned(VOID_VALUE) + IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IFNULL, IFNONNULL -> { + if (interpreter.checkUnaryCondition(frame.getStack(0)!!, insnOpcode)) { + goto((currentInsn as JumpInsnNode).label) + continue + } + } + IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE -> { + if (interpreter.checkBinaryCondition(frame.getStack(0)!!, frame.getStack(1)!!, insnOpcode)) { + goto((currentInsn as JumpInsnNode).label) + continue + } + } + + // TODO: try/catch/finally + ATHROW -> { + val exceptionValue = frame.getStack(0)!! + val handled = handler.exceptionThrown(frame, currentInsn, exceptionValue) + if (handled != null) return handled + return ExceptionThrown(exceptionValue) + } + + // Workaround for a bug in Kotlin: NoPatterMatched exception is thrown otherwise! + else -> {} } - // TODO: try/catch/finally - ATHROW -> { - val exceptionValue = frame.getStack(0)!! - val handled = handler.exceptionThrown(frame, currentInsn, exceptionValue) + try { + frame.execute(currentInsn, interpreter) + } + catch (e: ThrownFromEvalException) { + // TODO: try/catch.finaly + val handled = handler.exceptionThrown(frame, currentInsn, e.exception) if (handled != null) return handled - return ExceptionThrown(exceptionValue) + return ExceptionThrown(e.exception) } } } - try { - frame.execute(currentInsn, interpreter) - } - catch (e: ThrownFromEvalException) { - // TODO: try/catch.finaly - val handled = handler.exceptionThrown(frame, currentInsn, e.exception) - if (handled != null) return handled - return ExceptionThrown(e.exception) - } - val handled = handler.instructionProcessed(currentInsn) if (handled != null) return handled From b067b1abe46af716e4357a3856c9aee71d59c2c4 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 13:58:52 +0400 Subject: [PATCH 15/66] toString() supported for values and results --- src/org/jetbrains/eval4j/interpreterLoop.kt | 22 ++++++++++++++------ src/org/jetbrains/eval4j/values.kt | 23 ++++++++++++++------- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/org/jetbrains/eval4j/interpreterLoop.kt b/src/org/jetbrains/eval4j/interpreterLoop.kt index 06105bbf430..2fe8d3118ae 100644 --- a/src/org/jetbrains/eval4j/interpreterLoop.kt +++ b/src/org/jetbrains/eval4j/interpreterLoop.kt @@ -10,11 +10,21 @@ import org.objectweb.asm.tree.analysis.Interpreter import org.objectweb.asm.tree.JumpInsnNode import org.objectweb.asm.tree.VarInsnNode -trait InterpreterResult -object NOTHING_DONE: InterpreterResult -class ExceptionThrown(val exception: Value): InterpreterResult -class ValueReturned(val result: Value): InterpreterResult -class AbnormalTermination(val message: String): InterpreterResult +trait InterpreterResult { + fun toString(): String +} + +class ExceptionThrown(val exception: Value): InterpreterResult { + override fun toString(): String = "Thrown $exception" +} + +class ValueReturned(val result: Value): InterpreterResult { + override fun toString(): String = "Returned $result" +} + +class AbnormalTermination(val message: String): InterpreterResult { + override fun toString(): String = "Terminated abnormally: $message" +} trait InterpretationEventHandler { @@ -42,7 +52,7 @@ fun interpreterLoop( handler: InterpretationEventHandler = InterpretationEventHandler.NONE ): InterpreterResult { val firstInsn = m.instructions.getFirst() - if (firstInsn == null) return NOTHING_DONE + if (firstInsn == null) throw IllegalArgumentException("Empty method") var currentInsn = firstInsn!! diff --git a/src/org/jetbrains/eval4j/values.kt b/src/org/jetbrains/eval4j/values.kt index 80ac78f9e81..c00a49fde68 100644 --- a/src/org/jetbrains/eval4j/values.kt +++ b/src/org/jetbrains/eval4j/values.kt @@ -7,36 +7,45 @@ trait Value : org.objectweb.asm.tree.analysis.Value { val asmType: Type val valid: Boolean override fun getSize(): Int = asmType.getSize() + + override fun toString(): String } abstract class AbstractValue( - override val asmType: Type + override val asmType: Type, + private val asString: String ) : Value { override val valid = true + + override fun toString() = asString } object NOT_A_VALUE: Value { override val asmType = Type.getType("") override val valid = false override fun getSize(): Int = 1 + + override fun toString() = "NOT_A_VALUE" } object VOID_VALUE: Value { override val asmType: Type = Type.VOID_TYPE override val valid: Boolean = false + override fun toString() = "VOID_VALUE" } class NotInitialized(override val asmType: Type): Value { override val valid = false + override fun toString() = "NotInitialized: $asmType" } -class IntValue(val value: Int, asmType: Type): AbstractValue(asmType) -class LongValue(val value: Long): AbstractValue(Type.LONG_TYPE) -class FloatValue(val value: Float): AbstractValue(Type.FLOAT_TYPE) -class DoubleValue(val value: Double): AbstractValue(Type.DOUBLE_TYPE) -class ObjectValue(val value: Any?, asmType: Type): AbstractValue(asmType) +class IntValue(val value: Int, asmType: Type): AbstractValue(asmType, "$value") +class LongValue(val value: Long): AbstractValue(Type.LONG_TYPE, "$value") +class FloatValue(val value: Float): AbstractValue(Type.FLOAT_TYPE, "$value") +class DoubleValue(val value: Double): AbstractValue(Type.DOUBLE_TYPE, "$value") +class ObjectValue(val value: Any?, asmType: Type): AbstractValue(asmType, "$value") -class LabelValue(val value: LabelNode): AbstractValue(Type.VOID_TYPE) +class LabelValue(val value: LabelNode): AbstractValue(Type.VOID_TYPE, "label: ${value.getLabel()}") fun boolean(v: Boolean) = IntValue(if (v) 1 else 0, Type.BOOLEAN_TYPE) fun byte(v: Byte) = IntValue(v.toInt(), Type.BYTE_TYPE) From a55c350c0c3fe8752506268c4130f60dad4a1fd0 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 14:18:55 +0400 Subject: [PATCH 16/66] Added dependency on junit --- .idea/libraries/junit_junit_4_11.xml | 13 +++++++++++++ eval4j.iml | 1 + 2 files changed, 14 insertions(+) create mode 100644 .idea/libraries/junit_junit_4_11.xml diff --git a/.idea/libraries/junit_junit_4_11.xml b/.idea/libraries/junit_junit_4_11.xml new file mode 100644 index 00000000000..2895188a94b --- /dev/null +++ b/.idea/libraries/junit_junit_4_11.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/eval4j.iml b/eval4j.iml index ae1a999ff72..14b55ac72e5 100644 --- a/eval4j.iml +++ b/eval4j.iml @@ -9,6 +9,7 @@ + From ffca4dd5a0bac3b1ebc7acca76b0e27fd962e720 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 14:27:43 +0400 Subject: [PATCH 17/66] Setting up equals()/hashCode() for values and results --- src/org/jetbrains/eval4j/interpreterLoop.kt | 2 +- src/org/jetbrains/eval4j/values.kt | 40 +++++++++++++-------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/org/jetbrains/eval4j/interpreterLoop.kt b/src/org/jetbrains/eval4j/interpreterLoop.kt index 2fe8d3118ae..62c4ca22891 100644 --- a/src/org/jetbrains/eval4j/interpreterLoop.kt +++ b/src/org/jetbrains/eval4j/interpreterLoop.kt @@ -18,7 +18,7 @@ class ExceptionThrown(val exception: Value): InterpreterResult { override fun toString(): String = "Thrown $exception" } -class ValueReturned(val result: Value): InterpreterResult { +data class ValueReturned(val result: Value): InterpreterResult { override fun toString(): String = "Returned $result" } diff --git a/src/org/jetbrains/eval4j/values.kt b/src/org/jetbrains/eval4j/values.kt index c00a49fde68..dec8e19ade2 100644 --- a/src/org/jetbrains/eval4j/values.kt +++ b/src/org/jetbrains/eval4j/values.kt @@ -11,15 +11,6 @@ trait Value : org.objectweb.asm.tree.analysis.Value { override fun toString(): String } -abstract class AbstractValue( - override val asmType: Type, - private val asString: String -) : Value { - override val valid = true - - override fun toString() = asString -} - object NOT_A_VALUE: Value { override val asmType = Type.getType("") override val valid = false @@ -39,13 +30,32 @@ class NotInitialized(override val asmType: Type): Value { override fun toString() = "NotInitialized: $asmType" } -class IntValue(val value: Int, asmType: Type): AbstractValue(asmType, "$value") -class LongValue(val value: Long): AbstractValue(Type.LONG_TYPE, "$value") -class FloatValue(val value: Float): AbstractValue(Type.FLOAT_TYPE, "$value") -class DoubleValue(val value: Double): AbstractValue(Type.DOUBLE_TYPE, "$value") -class ObjectValue(val value: Any?, asmType: Type): AbstractValue(asmType, "$value") +abstract class AbstractValue( + public val value: V, + override val asmType: Type +) : Value { + override val valid = true -class LabelValue(val value: LabelNode): AbstractValue(Type.VOID_TYPE, "label: ${value.getLabel()}") + override fun toString() = "$value: $asmType" + + override fun equals(other: Any?): Boolean { + if (other !is AbstractValue<*>) return false + + return value == other.value && asmType == other.asmType + } + + override fun hashCode(): Int { + return value.hashCode() + 17 * asmType.hashCode() + } +} + +class IntValue(value: Int, asmType: Type): AbstractValue(value, asmType) +class LongValue(value: Long): AbstractValue(value, Type.LONG_TYPE) +class FloatValue(value: Float): AbstractValue(value, Type.FLOAT_TYPE) +class DoubleValue(value: Double): AbstractValue(value, Type.DOUBLE_TYPE) +class ObjectValue(value: Any?, asmType: Type): AbstractValue(value, asmType) + +class LabelValue(value: LabelNode): AbstractValue(value, Type.VOID_TYPE) fun boolean(v: Boolean) = IntValue(if (v) 1 else 0, Type.BOOLEAN_TYPE) fun byte(v: Byte) = IntValue(v.toInt(), Type.BYTE_TYPE) From 5a075b80d58bfef89822f4dd16925ec63e88bcc6 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 14:41:29 +0400 Subject: [PATCH 18/66] Testing infrastructure --- eval4j.iml | 1 + test/org/jetbrains/eval4j/test/TestData.java | 9 ++ test/org/jetbrains/eval4j/test/main.kt | 137 +++++++++++++++++++ 3 files changed, 147 insertions(+) create mode 100644 test/org/jetbrains/eval4j/test/TestData.java create mode 100644 test/org/jetbrains/eval4j/test/main.kt diff --git a/eval4j.iml b/eval4j.iml index 14b55ac72e5..cc20df994ed 100644 --- a/eval4j.iml +++ b/eval4j.iml @@ -4,6 +4,7 @@ + diff --git a/test/org/jetbrains/eval4j/test/TestData.java b/test/org/jetbrains/eval4j/test/TestData.java new file mode 100644 index 00000000000..66593712f11 --- /dev/null +++ b/test/org/jetbrains/eval4j/test/TestData.java @@ -0,0 +1,9 @@ +package org.jetbrains.eval4j.test; + +public class TestData { + public void imNotAPAckage() {} + + public static int testReturn() { + return 2; + } +} diff --git a/test/org/jetbrains/eval4j/test/main.kt b/test/org/jetbrains/eval4j/test/main.kt new file mode 100644 index 00000000000..d651f6f4c4b --- /dev/null +++ b/test/org/jetbrains/eval4j/test/main.kt @@ -0,0 +1,137 @@ +package org.jetbrains.eval4j.test + +import org.objectweb.asm.* +import org.objectweb.asm.Opcodes.* +import org.objectweb.asm.tree.MethodNode +import java.lang.reflect.Modifier +import org.jetbrains.eval4j.* +import org.junit.Assert.* +import junit.framework.TestSuite +import junit.framework.TestCase + +fun suite(): TestSuite { + val suite = TestSuite() + + val ownerClass = javaClass() + val inputStream = ownerClass.getClassLoader()!!.getResourceAsStream(ownerClass.getInternalName() + ".class")!! + + ClassReader(inputStream).accept(object : ClassVisitor(ASM4) { + + override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array?): MethodVisitor? { + return object : MethodNode(access, name, desc, signature, exceptions) { + override fun visitEnd() { + val testCase = doTest(ownerClass, this) + if (testCase != null) { + suite.addTest(testCase) + } + } + } + } + }, 0) + + return suite +} + +fun Class<*>.getInternalName(): String = Type.getType(this).getInternalName() + +fun doTest(ownerClass: Class, methodNode: MethodNode): TestCase? { + var expected: InterpreterResult? = null + for (method in ownerClass.getMethods()) { + if (method.getName() == methodNode.name) { + if ((method.getModifiers() and Modifier.STATIC) == 0) { + println("Skipping instance method: $method") + } + else if (method.getParameterTypes()!!.size > 0) { + println("Skipping method with parameters: $method") + } + else { + method.setAccessible(true) + val result = method.invoke(null) + val returnType = Type.getType(method.getReturnType()!!) + expected = when (returnType.getSort()) { + Type.VOID -> ValueReturned(VOID_VALUE) + Type.BOOLEAN -> ValueReturned(boolean(result as Boolean)) + Type.BYTE -> ValueReturned(byte(result as Byte)) + Type.SHORT -> ValueReturned(short(result as Short)) + Type.CHAR -> ValueReturned(char(result as Char)) + Type.INT -> ValueReturned(int(result as Int)) + Type.LONG -> ValueReturned(long(result as Long)) + Type.DOUBLE -> ValueReturned(double(result as Double)) + Type.FLOAT -> ValueReturned(float(result as Float)) + Type.OBJECT -> ValueReturned(obj(result)) + else -> { + println("Unsupported result type: $returnType") + return null + } + } + println("Testing $method") + } + } + } + + if (expected == null) { + println("Method not found: ${methodNode.name}") + return null + } + + return object : TestCase(methodNode.name) { + + override fun runTest() { + val value = interpreterLoop( + ownerClass.getInternalName(), + methodNode, + REFLECTION_EVAL + ) + + assertEquals(value, expected) + } + } +} + +object REFLECTION_EVAL : Eval { + override fun loadClass(classType: Type): Value { + throw UnsupportedOperationException() + } + override fun newInstance(classType: Type): Value { + throw UnsupportedOperationException() + } + override fun checkCast(value: Value, targetType: Type): Value { + throw UnsupportedOperationException() + } + override fun isInsetanceOf(value: Value, targetType: Type): Boolean { + throw UnsupportedOperationException() + } + override fun newArray(arrayType: Type, size: Int): Value { + throw UnsupportedOperationException() + } + override fun newMultiDimensionalArray(arrayType: Type, dimensionSizes: List): Value { + throw UnsupportedOperationException() + } + override fun getArrayLength(array: Value): Value { + throw UnsupportedOperationException() + } + override fun getArrayElement(array: Value, index: Value): Value { + throw UnsupportedOperationException() + } + override fun setArrayElement(array: Value, index: Value, newValue: Value) { + throw UnsupportedOperationException() + } + override fun getStaticField(fieldDesc: String): Value { + throw UnsupportedOperationException() + } + override fun setStaticField(fieldDesc: String, newValue: Value) { + throw UnsupportedOperationException() + } + override fun invokeStaticMethod(methodDesc: String, arguments: List): Value { + throw UnsupportedOperationException() + } + override fun getField(instance: Value, fieldDesc: String): Value { + throw UnsupportedOperationException() + } + override fun setField(instance: Value, fieldDesc: String, newValue: Value) { + throw UnsupportedOperationException() + } + override fun invokeMethod(instance: Value, methodDesc: String, arguments: List, invokespecial: Boolean): Value { + throw UnsupportedOperationException() + } +} \ No newline at end of file From 85ac477b2ffe257d9ba26a8aa91e5b1ededf5940 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 14:42:47 +0400 Subject: [PATCH 19/66] assertEquals(): order fixed --- test/org/jetbrains/eval4j/test/main.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/org/jetbrains/eval4j/test/main.kt b/test/org/jetbrains/eval4j/test/main.kt index d651f6f4c4b..90968a10a98 100644 --- a/test/org/jetbrains/eval4j/test/main.kt +++ b/test/org/jetbrains/eval4j/test/main.kt @@ -83,7 +83,7 @@ fun doTest(ownerClass: Class, methodNode: MethodNode): TestCase? { REFLECTION_EVAL ) - assertEquals(value, expected) + assertEquals(expected, value) } } } From e85a6fda3934e4382f07abc0e3dc953137e475d1 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 14:53:13 +0400 Subject: [PATCH 20/66] Support int coercions on return --- src/org/jetbrains/eval4j/interpreterLoop.kt | 19 +++++++++-- src/org/jetbrains/eval4j/values.kt | 7 +---- test/org/jetbrains/eval4j/test/TestData.java | 33 ++++++++++++++++++-- 3 files changed, 49 insertions(+), 10 deletions(-) diff --git a/src/org/jetbrains/eval4j/interpreterLoop.kt b/src/org/jetbrains/eval4j/interpreterLoop.kt index 62c4ca22891..7761a345c4e 100644 --- a/src/org/jetbrains/eval4j/interpreterLoop.kt +++ b/src/org/jetbrains/eval4j/interpreterLoop.kt @@ -1,6 +1,5 @@ package org.jetbrains.eval4j -import org.objectweb.asm.tree.LabelNode import org.objectweb.asm.tree.AbstractInsnNode import org.objectweb.asm.tree.analysis.Frame import org.objectweb.asm.tree.MethodNode @@ -95,7 +94,23 @@ fun interpreterLoop( LOOKUPSWITCH -> UnsupportedByteCodeException("LOOKUPSWITCH is not supported yet") TABLESWITCH -> UnsupportedByteCodeException("TABLESWITCH is not supported yet") - IRETURN, LRETURN, FRETURN, DRETURN, ARETURN -> return ValueReturned(frame.getStack(0)!!) + IRETURN, LRETURN, FRETURN, DRETURN, ARETURN -> { + val value = frame.getStack(0)!! + val expectedType = Type.getReturnType(m.desc) + if (value.asmType != expectedType) { + assert(insnOpcode == IRETURN, "Only ints should be coerced") + + val coerced = when (expectedType.getSort()) { + Type.BOOLEAN -> boolean(value.boolean) + Type.BYTE -> byte(value.int.toByte()) + Type.SHORT -> short(value.int.toShort()) + Type.CHAR -> char(value.int.toChar()) + else -> throw UnsupportedByteCodeException("Should not be coerced: $expectedType") + } + return ValueReturned(coerced) + } + return ValueReturned(value) + } RETURN -> return ValueReturned(VOID_VALUE) IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IFNULL, IFNONNULL -> { if (interpreter.checkUnaryCondition(frame.getStack(0)!!, insnOpcode)) { diff --git a/src/org/jetbrains/eval4j/values.kt b/src/org/jetbrains/eval4j/values.kt index dec8e19ade2..d4235c3273c 100644 --- a/src/org/jetbrains/eval4j/values.kt +++ b/src/org/jetbrains/eval4j/values.kt @@ -69,12 +69,7 @@ fun obj(v: T, t: Type = if (v != null) Type.getType(v.javaClass) else Type.ge val NULL_VALUE = ObjectValue(null, Type.getObjectType("null")) -val Value.boolean: Boolean - get(): Boolean { - assert(this.asmType == Type.BOOLEAN_TYPE) - return (this as IntValue).value == 1 - } - +val Value.boolean: Boolean get() = (this as IntValue).value == 1 val Value.int: Int get() = (this as IntValue).value val Value.long: Long get() = (this as LongValue).value val Value.float: Float get() = (this as FloatValue).value diff --git a/test/org/jetbrains/eval4j/test/TestData.java b/test/org/jetbrains/eval4j/test/TestData.java index 66593712f11..0686b915ddd 100644 --- a/test/org/jetbrains/eval4j/test/TestData.java +++ b/test/org/jetbrains/eval4j/test/TestData.java @@ -1,9 +1,38 @@ package org.jetbrains.eval4j.test; public class TestData { - public void imNotAPAckage() {} + public static void testReturnVoid() { + } - public static int testReturn() { + public static boolean testReturnBoolean() { + return true; + } + + public static byte testReturnByte() { return 2; } + + public static short testReturnShort() { + return 2; + } + + public static char testReturnChar() { + return '2'; + } + + public static int testReturnInt() { + return 2; + } + + public static long testReturnLong() { + return 2; + } + + public static float testReturnFloat() { + return 2.0f; + } + + public static double testReturnDouble() { + return 2.0d; + } } From c3766197904de82ef46aa59e9230e4e126277807 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 14:56:40 +0400 Subject: [PATCH 21/66] Allow omitting "public" and "test" --- .../org/objectweb/asm/tree/annotations.xml | 3 +++ test/org/jetbrains/eval4j/test/TestData.java | 20 +++++++++---------- test/org/jetbrains/eval4j/test/main.kt | 4 ++-- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml index 8e4c3eca809..f66428094e3 100644 --- a/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml +++ b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml @@ -347,6 +347,9 @@ + + + diff --git a/test/org/jetbrains/eval4j/test/TestData.java b/test/org/jetbrains/eval4j/test/TestData.java index 0686b915ddd..aaba8cefad3 100644 --- a/test/org/jetbrains/eval4j/test/TestData.java +++ b/test/org/jetbrains/eval4j/test/TestData.java @@ -1,38 +1,38 @@ package org.jetbrains.eval4j.test; -public class TestData { - public static void testReturnVoid() { +class TestData { + static void returnVoid() { } - public static boolean testReturnBoolean() { + static boolean returnBoolean() { return true; } - public static byte testReturnByte() { + static byte returnByte() { return 2; } - public static short testReturnShort() { + static short returnShort() { return 2; } - public static char testReturnChar() { + static char returnChar() { return '2'; } - public static int testReturnInt() { + static int returnInt() { return 2; } - public static long testReturnLong() { + static long returnLong() { return 2; } - public static float testReturnFloat() { + static float returnFloat() { return 2.0f; } - public static double testReturnDouble() { + static double returnDouble() { return 2.0d; } } diff --git a/test/org/jetbrains/eval4j/test/main.kt b/test/org/jetbrains/eval4j/test/main.kt index 90968a10a98..7b40eeb314d 100644 --- a/test/org/jetbrains/eval4j/test/main.kt +++ b/test/org/jetbrains/eval4j/test/main.kt @@ -36,7 +36,7 @@ fun Class<*>.getInternalName(): String = Type.getType(this).getInternalName() fun doTest(ownerClass: Class, methodNode: MethodNode): TestCase? { var expected: InterpreterResult? = null - for (method in ownerClass.getMethods()) { + for (method in ownerClass.getDeclaredMethods()) { if (method.getName() == methodNode.name) { if ((method.getModifiers() and Modifier.STATIC) == 0) { println("Skipping instance method: $method") @@ -74,7 +74,7 @@ fun doTest(ownerClass: Class, methodNode: MethodNode): TestCase? { return null } - return object : TestCase(methodNode.name) { + return object : TestCase("test" + methodNode.name.capitalize()) { override fun runTest() { val value = interpreterLoop( From 55e861e503e24b81e8e836a80aabd8764e0bf16c Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 15:07:58 +0400 Subject: [PATCH 22/66] Don't forget to pop the condition off the stack --- src/org/jetbrains/eval4j/interpreterLoop.kt | 2 ++ test/org/jetbrains/eval4j/test/TestData.java | 27 ++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/org/jetbrains/eval4j/interpreterLoop.kt b/src/org/jetbrains/eval4j/interpreterLoop.kt index 7761a345c4e..b3edf0705ec 100644 --- a/src/org/jetbrains/eval4j/interpreterLoop.kt +++ b/src/org/jetbrains/eval4j/interpreterLoop.kt @@ -114,12 +114,14 @@ fun interpreterLoop( RETURN -> return ValueReturned(VOID_VALUE) IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IFNULL, IFNONNULL -> { if (interpreter.checkUnaryCondition(frame.getStack(0)!!, insnOpcode)) { + frame.execute(currentInsn, interpreter) goto((currentInsn as JumpInsnNode).label) continue } } IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE -> { if (interpreter.checkBinaryCondition(frame.getStack(0)!!, frame.getStack(1)!!, insnOpcode)) { + frame.execute(currentInsn, interpreter) goto((currentInsn as JumpInsnNode).label) continue } diff --git a/test/org/jetbrains/eval4j/test/TestData.java b/test/org/jetbrains/eval4j/test/TestData.java index aaba8cefad3..693e07a4e6d 100644 --- a/test/org/jetbrains/eval4j/test/TestData.java +++ b/test/org/jetbrains/eval4j/test/TestData.java @@ -35,4 +35,31 @@ class TestData { static double returnDouble() { return 2.0d; } + + static int variable() { + int i = 153; + return i; + } + + static int unaryMinus() { + int i = 153; + return -i; + } + + static int ifThen() { + boolean a = true; + if (a) + return 2; + return 1; + } + + static int ifElse() { + boolean a = false; + if (a) { + return 2; + } + else { + return 1; + } + } } From 632adb595cfa6ed0e17eeb790100418c1a787656 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 15:13:52 +0400 Subject: [PATCH 23/66] Some tests for loops --- test/org/jetbrains/eval4j/test/TestData.java | 69 ++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/test/org/jetbrains/eval4j/test/TestData.java b/test/org/jetbrains/eval4j/test/TestData.java index 693e07a4e6d..60ffb645e71 100644 --- a/test/org/jetbrains/eval4j/test/TestData.java +++ b/test/org/jetbrains/eval4j/test/TestData.java @@ -62,4 +62,73 @@ class TestData { return 1; } } + + static int loop() { + int i = 0; + while (i < 10) i++; + return i; + } + + static int loopWithBreak() { + int i = 0; + while (true) { + if (i > 10) break; + i++; + } + return i; + } + + static int loopWithReturn() { + int i = 0; + while (true) { + if (i > 10) return i; + i++; + } + } + + static int testSimpleFinally() { + int i = 5; + try { + return i; + } + finally { + i = 3; + } + } + + static int testSimpleFinallyWithReturn() { + int i = 5; + try { + return i; + } + finally { + return 3; + } + } + + static int testSimpleFinallyWithContinueInLoop() { + int i = 5; + while (true) { + try { + if (i % 2 == 0) continue; + if (i > 10) return i; + } + finally { + i++; + } + } + } + + static int testSimpleFinallyWithBreakInLoop() { + int i = 5; + while (true) { + try { + if (i % 2 == 0) break; + } + finally { + i++; + } + } + return i; + } } From 173e7157abfb37695ec6393104b84f8f00135f23 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 15:16:31 +0400 Subject: [PATCH 24/66] ReadMe --- ReadMe.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 ReadMe.md diff --git a/ReadMe.md b/ReadMe.md new file mode 100644 index 00000000000..9fbab56cb9b --- /dev/null +++ b/ReadMe.md @@ -0,0 +1,2 @@ +eval4j is a Java byte code interpreter written in Java. +Its primary use case is implementing expression evaluation in debuggers. \ No newline at end of file From c5cddd42cb2ec2888fd800d1e5e422a034cceaf4 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 15:19:04 +0400 Subject: [PATCH 25/66] .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000000..673af156045 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +out +.idea/workspace.xml +.idea/uiDesigner.xml From 9b2190e1b9046037e2f6b1fa385fdde19c1d3fc1 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 16:30:30 +0400 Subject: [PATCH 26/66] All objects should be loaded by Eval and not directly --- src/org/jetbrains/eval4j/interpreter.kt | 3 ++- src/org/jetbrains/eval4j/interpreterLoop.kt | 2 +- src/org/jetbrains/eval4j/values.kt | 2 +- test/org/jetbrains/eval4j/test/TestData.java | 9 +++++++++ test/org/jetbrains/eval4j/test/main.kt | 4 +++- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/org/jetbrains/eval4j/interpreter.kt b/src/org/jetbrains/eval4j/interpreter.kt index 6afb6f85535..e6a75c4ffb1 100644 --- a/src/org/jetbrains/eval4j/interpreter.kt +++ b/src/org/jetbrains/eval4j/interpreter.kt @@ -19,6 +19,7 @@ class UnsupportedByteCodeException(message: String) : RuntimeException(message) trait Eval { fun loadClass(classType: Type): Value + fun loadString(str: String): Value fun newInstance(classType: Type): Value fun checkCast(value: Value, targetType: Type): Value fun isInsetanceOf(value: Value, targetType: Type): Boolean @@ -83,7 +84,7 @@ class SingleInstructionInterpreter(private val eval: Eval) : Interpreter( is Float -> float(cst) is Long -> long(cst) is Double -> double(cst) - is String -> obj(cst) + is String -> eval.loadString(cst) is Type -> { val sort = (cst as Type).getSort() when (sort) { diff --git a/src/org/jetbrains/eval4j/interpreterLoop.kt b/src/org/jetbrains/eval4j/interpreterLoop.kt index b3edf0705ec..d4fd3e120a6 100644 --- a/src/org/jetbrains/eval4j/interpreterLoop.kt +++ b/src/org/jetbrains/eval4j/interpreterLoop.kt @@ -97,7 +97,7 @@ fun interpreterLoop( IRETURN, LRETURN, FRETURN, DRETURN, ARETURN -> { val value = frame.getStack(0)!! val expectedType = Type.getReturnType(m.desc) - if (value.asmType != expectedType) { + if (value != NULL_VALUE && value.asmType != expectedType) { assert(insnOpcode == IRETURN, "Only ints should be coerced") val coerced = when (expectedType.getSort()) { diff --git a/src/org/jetbrains/eval4j/values.kt b/src/org/jetbrains/eval4j/values.kt index d4235c3273c..bde6987d79c 100644 --- a/src/org/jetbrains/eval4j/values.kt +++ b/src/org/jetbrains/eval4j/values.kt @@ -65,7 +65,7 @@ fun int(v: Int) = IntValue(v, Type.INT_TYPE) fun long(v: Long) = LongValue(v) fun float(v: Float) = FloatValue(v) fun double(v: Double) = DoubleValue(v) -fun obj(v: T, t: Type = if (v != null) Type.getType(v.javaClass) else Type.getType(javaClass())) = ObjectValue(v, t) +//fun obj(v: T, t: Type = if (v != null) Type.getType(v.javaClass) else Type.getType(javaClass())) = ObjectValue(v, t) val NULL_VALUE = ObjectValue(null, Type.getObjectType("null")) diff --git a/test/org/jetbrains/eval4j/test/TestData.java b/test/org/jetbrains/eval4j/test/TestData.java index 60ffb645e71..0e4b1524926 100644 --- a/test/org/jetbrains/eval4j/test/TestData.java +++ b/test/org/jetbrains/eval4j/test/TestData.java @@ -36,6 +36,14 @@ class TestData { return 2.0d; } + static Object returnNull() { + return null; + } + + static String returnString() { + return "str"; + } + static int variable() { int i = 153; return i; @@ -131,4 +139,5 @@ class TestData { } return i; } + } diff --git a/test/org/jetbrains/eval4j/test/main.kt b/test/org/jetbrains/eval4j/test/main.kt index 7b40eeb314d..5ea8a7f1099 100644 --- a/test/org/jetbrains/eval4j/test/main.kt +++ b/test/org/jetbrains/eval4j/test/main.kt @@ -58,7 +58,7 @@ fun doTest(ownerClass: Class, methodNode: MethodNode): TestCase? { Type.LONG -> ValueReturned(long(result as Long)) Type.DOUBLE -> ValueReturned(double(result as Double)) Type.FLOAT -> ValueReturned(float(result as Float)) - Type.OBJECT -> ValueReturned(obj(result)) + Type.OBJECT -> ValueReturned(if (result == null) NULL_VALUE else ObjectValue(result, returnType)) else -> { println("Unsupported result type: $returnType") return null @@ -92,6 +92,8 @@ object REFLECTION_EVAL : Eval { override fun loadClass(classType: Type): Value { throw UnsupportedOperationException() } + override fun loadString(str: String): Value = ObjectValue(str, Type.getType(javaClass())) + override fun newInstance(classType: Type): Value { throw UnsupportedOperationException() } From 8d29047204847df87df9228f60b983d759c62b74 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 17:43:56 +0400 Subject: [PATCH 27/66] Run configuration for tests --- .idea/runConfigurations/Test.xml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .idea/runConfigurations/Test.xml diff --git a/.idea/runConfigurations/Test.xml b/.idea/runConfigurations/Test.xml new file mode 100644 index 00000000000..62cf139b6e9 --- /dev/null +++ b/.idea/runConfigurations/Test.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + \ No newline at end of file From cec2111c29ff6ced7c47330e2d6503ff944d6ab6 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 17:44:37 +0400 Subject: [PATCH 28/66] Coerce object types on return --- src/org/jetbrains/eval4j/interpreterLoop.kt | 11 +++++++++-- test/org/jetbrains/eval4j/test/TestData.java | 4 ++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/org/jetbrains/eval4j/interpreterLoop.kt b/src/org/jetbrains/eval4j/interpreterLoop.kt index d4fd3e120a6..6762f9c4501 100644 --- a/src/org/jetbrains/eval4j/interpreterLoop.kt +++ b/src/org/jetbrains/eval4j/interpreterLoop.kt @@ -8,6 +8,7 @@ import org.objectweb.asm.Opcodes.* import org.objectweb.asm.tree.analysis.Interpreter import org.objectweb.asm.tree.JumpInsnNode import org.objectweb.asm.tree.VarInsnNode +import org.objectweb.asm.util.Printer trait InterpreterResult { fun toString(): String @@ -97,8 +98,14 @@ fun interpreterLoop( IRETURN, LRETURN, FRETURN, DRETURN, ARETURN -> { val value = frame.getStack(0)!! val expectedType = Type.getReturnType(m.desc) - if (value != NULL_VALUE && value.asmType != expectedType) { - assert(insnOpcode == IRETURN, "Only ints should be coerced") + if (expectedType.getSort() == Type.OBJECT) { + val coerced = if (value != NULL_VALUE && value.asmType != expectedType) + ObjectValue(value.obj, expectedType) + else value + return ValueReturned(coerced) + } + if (value.asmType != expectedType) { + assert(insnOpcode == IRETURN, "Only ints should be coerced: " + Printer.OPCODES[insnOpcode]) val coerced = when (expectedType.getSort()) { Type.BOOLEAN -> boolean(value.boolean) diff --git a/test/org/jetbrains/eval4j/test/TestData.java b/test/org/jetbrains/eval4j/test/TestData.java index 0e4b1524926..71a5acbd233 100644 --- a/test/org/jetbrains/eval4j/test/TestData.java +++ b/test/org/jetbrains/eval4j/test/TestData.java @@ -44,6 +44,10 @@ class TestData { return "str"; } + static Object returnStringAsObject() { + return "str"; + } + static int variable() { int i = 153; return i; From b7c169fc89cec788713d8de0171ff6298410470d Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 17:46:23 +0400 Subject: [PATCH 29/66] Support static method calls --- .../org/objectweb/asm/tree/annotations.xml | 6 ++ src/org/jetbrains/eval4j/interpreter.kt | 31 ++++-- src/org/jetbrains/eval4j/values.kt | 2 +- test/org/jetbrains/eval4j/test/TestData.java | 8 ++ test/org/jetbrains/eval4j/test/main.kt | 95 +++++++++++++++---- 5 files changed, 115 insertions(+), 27 deletions(-) diff --git a/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml index f66428094e3..924d69cca1f 100644 --- a/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml +++ b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml @@ -329,6 +329,9 @@ + + + @@ -338,6 +341,9 @@ + + + diff --git a/src/org/jetbrains/eval4j/interpreter.kt b/src/org/jetbrains/eval4j/interpreter.kt index e6a75c4ffb1..528606163b5 100644 --- a/src/org/jetbrains/eval4j/interpreter.kt +++ b/src/org/jetbrains/eval4j/interpreter.kt @@ -13,7 +13,27 @@ import org.objectweb.asm.tree.MultiANewArrayInsnNode import org.objectweb.asm.tree.TypeInsnNode import org.objectweb.asm.tree.JumpInsnNode import org.objectweb.asm.tree.IincInsnNode -import org.objectweb.asm.tree.LabelNode + +data class MethodDescription( + val ownerInternalName: String, + val name: String, + val desc: String, + val isStatic: Boolean +) + +fun MethodDescription(insn: MethodInsnNode): MethodDescription = + MethodDescription( + insn.owner, + insn.name, + insn.desc, + insn.getOpcode() == INVOKESTATIC + ) + +val MethodDescription.returnType: Type + get() = Type.getReturnType(desc) + +val MethodDescription.parameterTypes: List + get() = Type.getArgumentTypes(desc).toList() class UnsupportedByteCodeException(message: String) : RuntimeException(message) @@ -32,11 +52,11 @@ trait Eval { fun getStaticField(fieldDesc: String): Value fun setStaticField(fieldDesc: String, newValue: Value) - fun invokeStaticMethod(methodDesc: String, arguments: List): Value + fun invokeStaticMethod(methodDesc: MethodDescription, arguments: List): Value fun getField(instance: Value, fieldDesc: String): Value fun setField(instance: Value, fieldDesc: String, newValue: Value) - fun invokeMethod(instance: Value, methodDesc: String, arguments: List, invokespecial: Boolean = false): Value + fun invokeMethod(instance: Value, methodDesc: MethodDescription, arguments: List, invokespecial: Boolean = false): Value } class SingleInstructionInterpreter(private val eval: Eval) : Interpreter(ASM4) { @@ -334,16 +354,15 @@ class SingleInstructionInterpreter(private val eval: Eval) : Interpreter( } INVOKEVIRTUAL, INVOKESPECIAL, INVOKEINTERFACE -> { - val desc = (insn as MethodInsnNode).desc eval.invokeMethod( values[0], - desc, + MethodDescription(insn as MethodInsnNode), values.subList(1, values.size()), insn.getOpcode() == INVOKESPECIAL ) } - INVOKESTATIC -> eval.invokeStaticMethod((insn as MethodInsnNode).desc, values) + INVOKESTATIC -> eval.invokeStaticMethod(MethodDescription(insn as MethodInsnNode), values) INVOKEDYNAMIC -> throw UnsupportedByteCodeException("INVOKEDYNAMIC is not supported") else -> throw UnsupportedByteCodeException("$insn") diff --git a/src/org/jetbrains/eval4j/values.kt b/src/org/jetbrains/eval4j/values.kt index bde6987d79c..c0f93054736 100644 --- a/src/org/jetbrains/eval4j/values.kt +++ b/src/org/jetbrains/eval4j/values.kt @@ -74,4 +74,4 @@ val Value.int: Int get() = (this as IntValue).value val Value.long: Long get() = (this as LongValue).value val Value.float: Float get() = (this as FloatValue).value val Value.double: Double get() = (this as DoubleValue).value -val Value.obj: Any? get() = (this as ObjectValue).value \ No newline at end of file +val Value.obj: Any? get() = (this as AbstractValue<*>).value \ No newline at end of file diff --git a/test/org/jetbrains/eval4j/test/TestData.java b/test/org/jetbrains/eval4j/test/TestData.java index 71a5acbd233..ca446e9ace7 100644 --- a/test/org/jetbrains/eval4j/test/TestData.java +++ b/test/org/jetbrains/eval4j/test/TestData.java @@ -144,4 +144,12 @@ class TestData { return i; } + static Object testCall() { + return Integer.valueOf(1); + } + + static Object testCallWithObject() { + return String.valueOf("str"); + } + } diff --git a/test/org/jetbrains/eval4j/test/main.kt b/test/org/jetbrains/eval4j/test/main.kt index 5ea8a7f1099..158c3796cd9 100644 --- a/test/org/jetbrains/eval4j/test/main.kt +++ b/test/org/jetbrains/eval4j/test/main.kt @@ -8,6 +8,7 @@ import org.jetbrains.eval4j.* import org.junit.Assert.* import junit.framework.TestSuite import junit.framework.TestCase +import java.lang.reflect.Method fun suite(): TestSuite { val suite = TestSuite() @@ -48,23 +49,12 @@ fun doTest(ownerClass: Class, methodNode: MethodNode): TestCase? { method.setAccessible(true) val result = method.invoke(null) val returnType = Type.getType(method.getReturnType()!!) - expected = when (returnType.getSort()) { - Type.VOID -> ValueReturned(VOID_VALUE) - Type.BOOLEAN -> ValueReturned(boolean(result as Boolean)) - Type.BYTE -> ValueReturned(byte(result as Byte)) - Type.SHORT -> ValueReturned(short(result as Short)) - Type.CHAR -> ValueReturned(char(result as Char)) - Type.INT -> ValueReturned(int(result as Int)) - Type.LONG -> ValueReturned(long(result as Long)) - Type.DOUBLE -> ValueReturned(double(result as Double)) - Type.FLOAT -> ValueReturned(float(result as Float)) - Type.OBJECT -> ValueReturned(if (result == null) NULL_VALUE else ObjectValue(result, returnType)) - else -> { - println("Unsupported result type: $returnType") - return null - } + try { + expected = ValueReturned(objectToValue(result, returnType)) + } + catch (e: UnsupportedOperationException) { + println("Skipping $method: $e") } - println("Testing $method") } } } @@ -88,7 +78,26 @@ fun doTest(ownerClass: Class, methodNode: MethodNode): TestCase? { } } +fun objectToValue(obj: Any?, expectedType: Type): Value { + return when (expectedType.getSort()) { + Type.VOID -> VOID_VALUE + Type.BOOLEAN -> boolean(obj as Boolean) + Type.BYTE -> byte(obj as Byte) + Type.SHORT -> short(obj as Short) + Type.CHAR -> char(obj as Char) + Type.INT -> int(obj as Int) + Type.LONG -> long(obj as Long) + Type.DOUBLE -> double(obj as Double) + Type.FLOAT -> float(obj as Float) + Type.OBJECT -> if (obj == null) NULL_VALUE else ObjectValue(obj, expectedType) + else -> throw UnsupportedOperationException("Unsupported result type: $expectedType") + } +} + object REFLECTION_EVAL : Eval { + + val lookup = ReflectionLookup(javaClass().getClassLoader()!!) + override fun loadClass(classType: Type): Value { throw UnsupportedOperationException() } @@ -103,6 +112,7 @@ object REFLECTION_EVAL : Eval { override fun isInsetanceOf(value: Value, targetType: Type): Boolean { throw UnsupportedOperationException() } + override fun newArray(arrayType: Type, size: Int): Value { throw UnsupportedOperationException() } @@ -118,22 +128,67 @@ object REFLECTION_EVAL : Eval { override fun setArrayElement(array: Value, index: Value, newValue: Value) { throw UnsupportedOperationException() } + override fun getStaticField(fieldDesc: String): Value { throw UnsupportedOperationException() } override fun setStaticField(fieldDesc: String, newValue: Value) { throw UnsupportedOperationException() } - override fun invokeStaticMethod(methodDesc: String, arguments: List): Value { - throw UnsupportedOperationException() + + override fun invokeStaticMethod(methodDesc: MethodDescription, arguments: List): Value { + assertTrue(methodDesc.isStatic) + val owner = lookup.findClass(methodDesc.ownerInternalName) + assertNotNull("Class not found: ${methodDesc.ownerInternalName}", owner) + val method = owner!!.findMethod(methodDesc) + assertNotNull("Method not found: $methodDesc", method) + val result = method!!.invoke(null, *arguments.map { v -> v.obj }.copyToArray()) + return objectToValue(result, methodDesc.returnType) } + override fun getField(instance: Value, fieldDesc: String): Value { throw UnsupportedOperationException() } override fun setField(instance: Value, fieldDesc: String, newValue: Value) { throw UnsupportedOperationException() } - override fun invokeMethod(instance: Value, methodDesc: String, arguments: List, invokespecial: Boolean): Value { + override fun invokeMethod(instance: Value, methodDesc: MethodDescription, arguments: List, invokespecial: Boolean): Value { throw UnsupportedOperationException() } -} \ No newline at end of file +} + +class ReflectionLookup(val classLoader: ClassLoader) { + [suppress("UNCHECKED_CAST")] + fun findClass(internalName: String): Class? = classLoader.loadClass(internalName.replace('/', '.')) as Class +} + +[suppress("UNCHECKED_CAST")] +fun Class.findMethod(methodDesc: MethodDescription): Method? { + for (declared in getDeclaredMethods()) { + if (methodDesc.matches(declared)) return declared + } + + val fromSuperClass = (getSuperclass() as Class).findMethod(methodDesc) + if (fromSuperClass != null) return fromSuperClass + + for (supertype in getInterfaces()) { + val fromSuper = (supertype as Class).findMethod(methodDesc) + if (fromSuper != null) return fromSuper + } + + return null +} + +fun MethodDescription.matches(method: Method): Boolean { + if (name != method.getName()) return false + + val methodParams = method.getParameterTypes()!! + if (parameterTypes.size() != methodParams.size) return false + for ((i, p) in parameterTypes.withIndices()) { + if (!p.matches(methodParams[i])) return false + } + + return returnType.matches(method.getReturnType()!!) +} + +fun Type.matches(_class: Class<*>): Boolean = this == Type.getType(_class) \ No newline at end of file From 2e6d523966c489e692e56920081f406ff4da6885 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 17:59:06 +0400 Subject: [PATCH 30/66] Reading static fields supported --- .../org/objectweb/asm/tree/annotations.xml | 6 +++ src/org/jetbrains/eval4j/interpreter.kt | 37 +++---------- src/org/jetbrains/eval4j/members.kt | 53 +++++++++++++++++++ test/org/jetbrains/eval4j/test/TestData.java | 4 ++ test/org/jetbrains/eval4j/test/main.kt | 44 ++++++++++++--- 5 files changed, 109 insertions(+), 35 deletions(-) create mode 100644 src/org/jetbrains/eval4j/members.kt diff --git a/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml index 924d69cca1f..7866c5dc168 100644 --- a/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml +++ b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml @@ -116,6 +116,9 @@ + + + @@ -125,6 +128,9 @@ + + + diff --git a/src/org/jetbrains/eval4j/interpreter.kt b/src/org/jetbrains/eval4j/interpreter.kt index 528606163b5..38734a2e413 100644 --- a/src/org/jetbrains/eval4j/interpreter.kt +++ b/src/org/jetbrains/eval4j/interpreter.kt @@ -14,27 +14,6 @@ import org.objectweb.asm.tree.TypeInsnNode import org.objectweb.asm.tree.JumpInsnNode import org.objectweb.asm.tree.IincInsnNode -data class MethodDescription( - val ownerInternalName: String, - val name: String, - val desc: String, - val isStatic: Boolean -) - -fun MethodDescription(insn: MethodInsnNode): MethodDescription = - MethodDescription( - insn.owner, - insn.name, - insn.desc, - insn.getOpcode() == INVOKESTATIC - ) - -val MethodDescription.returnType: Type - get() = Type.getReturnType(desc) - -val MethodDescription.parameterTypes: List - get() = Type.getArgumentTypes(desc).toList() - class UnsupportedByteCodeException(message: String) : RuntimeException(message) trait Eval { @@ -50,12 +29,12 @@ trait Eval { fun getArrayElement(array: Value, index: Value): Value fun setArrayElement(array: Value, index: Value, newValue: Value) - fun getStaticField(fieldDesc: String): Value - fun setStaticField(fieldDesc: String, newValue: Value) + fun getStaticField(fieldDesc: FieldDescription): Value + fun setStaticField(fieldDesc: FieldDescription, newValue: Value) fun invokeStaticMethod(methodDesc: MethodDescription, arguments: List): Value - fun getField(instance: Value, fieldDesc: String): Value - fun setField(instance: Value, fieldDesc: String, newValue: Value) + fun getField(instance: Value, fieldDesc: FieldDescription): Value + fun setField(instance: Value, fieldDesc: FieldDescription, newValue: Value) fun invokeMethod(instance: Value, methodDesc: MethodDescription, arguments: List, invokespecial: Boolean = false): Value } @@ -118,7 +97,7 @@ class SingleInstructionInterpreter(private val eval: Eval) : Interpreter( } } JSR -> LabelValue((insn as JumpInsnNode).label) - GETSTATIC -> eval.getStaticField((insn as FieldInsnNode).desc) + GETSTATIC -> eval.getStaticField(FieldDescription(insn as FieldInsnNode)) NEW -> eval.newInstance(Type.getType((insn as TypeInsnNode).desc)) else -> throw UnsupportedByteCodeException("$insn") } @@ -164,11 +143,11 @@ class SingleInstructionInterpreter(private val eval: Eval) : Interpreter( LOOKUPSWITCH -> throw UnsupportedByteCodeException("Switch is not supported yet") PUTSTATIC -> { - eval.setStaticField((insn as FieldInsnNode).desc, value) + eval.setStaticField(FieldDescription(insn as FieldInsnNode), value) null } - GETFIELD -> eval.getField(value, (insn as FieldInsnNode).desc) + GETFIELD -> eval.getField(value, FieldDescription(insn as FieldInsnNode)) NEWARRAY -> { val typeStr = when ((insn as IntInsnNode).operand) { @@ -313,7 +292,7 @@ class SingleInstructionInterpreter(private val eval: Eval) : Interpreter( } PUTFIELD -> { - eval.setField(value1, (insn as FieldInsnNode).desc, value2) + eval.setField(value1, FieldDescription(insn as FieldInsnNode), value2) null } diff --git a/src/org/jetbrains/eval4j/members.kt b/src/org/jetbrains/eval4j/members.kt new file mode 100644 index 00000000000..4961ca0bee4 --- /dev/null +++ b/src/org/jetbrains/eval4j/members.kt @@ -0,0 +1,53 @@ +package org.jetbrains.eval4j + +import org.objectweb.asm.tree.MethodInsnNode +import org.objectweb.asm.Opcodes.* +import org.objectweb.asm.* +import org.objectweb.asm.tree.FieldInsnNode + +open data class MemberDescription( + val ownerInternalName: String, + val name: String, + val desc: String, + val isStatic: Boolean +) + +class MethodDescription( + ownerInternalName: String, + name: String, + desc: String, + isStatic: Boolean +) : MemberDescription(ownerInternalName, name, desc, isStatic) + +fun MethodDescription(insn: MethodInsnNode): MethodDescription = + MethodDescription( + insn.owner, + insn.name, + insn.desc, + insn.getOpcode() == INVOKESTATIC + ) + +val MethodDescription.returnType: Type + get() = Type.getReturnType(desc) + +val MethodDescription.parameterTypes: List + get() = Type.getArgumentTypes(desc).toList() + + +class FieldDescription( + ownerInternalName: String, + name: String, + desc: String, + isStatic: Boolean +) : MemberDescription(ownerInternalName, name, desc, isStatic) + +fun FieldDescription(insn: FieldInsnNode): FieldDescription = + FieldDescription( + insn.owner, + insn.name, + insn.desc, + insn.getOpcode() in setOf(GETSTATIC, PUTSTATIC) + ) + +val FieldDescription.fieldType: Type + get() = Type.getType(desc) diff --git a/test/org/jetbrains/eval4j/test/TestData.java b/test/org/jetbrains/eval4j/test/TestData.java index ca446e9ace7..55c20d32cc0 100644 --- a/test/org/jetbrains/eval4j/test/TestData.java +++ b/test/org/jetbrains/eval4j/test/TestData.java @@ -152,4 +152,8 @@ class TestData { return String.valueOf("str"); } + static Object testGetStaticField() { + return String.CASE_INSENSITIVE_ORDER; + } + } diff --git a/test/org/jetbrains/eval4j/test/main.kt b/test/org/jetbrains/eval4j/test/main.kt index 158c3796cd9..5b958d5b225 100644 --- a/test/org/jetbrains/eval4j/test/main.kt +++ b/test/org/jetbrains/eval4j/test/main.kt @@ -9,6 +9,8 @@ import org.junit.Assert.* import junit.framework.TestSuite import junit.framework.TestCase import java.lang.reflect.Method +import java.lang.reflect.Field +import kotlin.test.assertNotNull fun suite(): TestSuite { val suite = TestSuite() @@ -129,11 +131,18 @@ object REFLECTION_EVAL : Eval { throw UnsupportedOperationException() } - override fun getStaticField(fieldDesc: String): Value { - throw UnsupportedOperationException() + override fun getStaticField(fieldDesc: FieldDescription): Value { + assertTrue(fieldDesc.isStatic) + val owner = lookup.findClass(fieldDesc.ownerInternalName) + assertNotNull("Class not found: ${fieldDesc.ownerInternalName}", owner) + val field = owner!!.findField(fieldDesc) + assertNotNull("Field not found: $fieldDesc", field) + val result = field!!.get(null) + return objectToValue(result, fieldDesc.fieldType) } - override fun setStaticField(fieldDesc: String, newValue: Value) { - throw UnsupportedOperationException() + + override fun setStaticField(fieldDesc: FieldDescription, newValue: Value) { + assertTrue(fieldDesc.isStatic) } override fun invokeStaticMethod(methodDesc: MethodDescription, arguments: List): Value { @@ -146,10 +155,10 @@ object REFLECTION_EVAL : Eval { return objectToValue(result, methodDesc.returnType) } - override fun getField(instance: Value, fieldDesc: String): Value { + override fun getField(instance: Value, fieldDesc: FieldDescription): Value { throw UnsupportedOperationException() } - override fun setField(instance: Value, fieldDesc: String, newValue: Value) { + override fun setField(instance: Value, fieldDesc: FieldDescription, newValue: Value) { throw UnsupportedOperationException() } override fun invokeMethod(instance: Value, methodDesc: MethodDescription, arguments: List, invokespecial: Boolean): Value { @@ -191,4 +200,27 @@ fun MethodDescription.matches(method: Method): Boolean { return returnType.matches(method.getReturnType()!!) } +[suppress("UNCHECKED_CAST")] +fun Class.findField(fieldDesc: FieldDescription): Field? { + for (declared in getDeclaredFields()) { + if (fieldDesc.matches(declared)) return declared + } + + val fromSuperClass = (getSuperclass() as Class).findField(fieldDesc) + if (fromSuperClass != null) return fromSuperClass + + for (supertype in getInterfaces()) { + val fromSuper = (supertype as Class).findField(fieldDesc) + if (fromSuper != null) return fromSuper + } + + return null +} + +fun FieldDescription.matches(field: Field): Boolean { + if (name != field.getName()) return false + + return fieldType.matches(field.getType()!!) +} + fun Type.matches(_class: Class<*>): Boolean = this == Type.getType(_class) \ No newline at end of file From 7d7316a077870b99032ae57bd2f7d471a5952335 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 18:01:12 +0400 Subject: [PATCH 31/66] Reading static fields supported --- test/org/jetbrains/eval4j/test/TestData.java | 10 ++++++++++ test/org/jetbrains/eval4j/test/main.kt | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/test/org/jetbrains/eval4j/test/TestData.java b/test/org/jetbrains/eval4j/test/TestData.java index 55c20d32cc0..39edc598bc7 100644 --- a/test/org/jetbrains/eval4j/test/TestData.java +++ b/test/org/jetbrains/eval4j/test/TestData.java @@ -156,4 +156,14 @@ class TestData { return String.CASE_INSENSITIVE_ORDER; } + static int FIELD = 0; + + static int testPutStaticField() { + FIELD = 5; + int f1 = FIELD; + FIELD = 6; + int f2 = FIELD; + return f2 + f1; + } + } diff --git a/test/org/jetbrains/eval4j/test/main.kt b/test/org/jetbrains/eval4j/test/main.kt index 5b958d5b225..d8fa29232c5 100644 --- a/test/org/jetbrains/eval4j/test/main.kt +++ b/test/org/jetbrains/eval4j/test/main.kt @@ -143,6 +143,11 @@ object REFLECTION_EVAL : Eval { override fun setStaticField(fieldDesc: FieldDescription, newValue: Value) { assertTrue(fieldDesc.isStatic) + val owner = lookup.findClass(fieldDesc.ownerInternalName) + assertNotNull("Class not found: ${fieldDesc.ownerInternalName}", owner) + val field = owner!!.findField(fieldDesc) + assertNotNull("Field not found: $fieldDesc", field) + val result = field!!.set(null, newValue.obj) } override fun invokeStaticMethod(methodDesc: MethodDescription, arguments: List): Value { From 6e597b0438bb1b61f7ba130c323f299de7805565 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 22:39:13 +0400 Subject: [PATCH 32/66] Unused variable removed --- test/org/jetbrains/eval4j/test/main.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/org/jetbrains/eval4j/test/main.kt b/test/org/jetbrains/eval4j/test/main.kt index d8fa29232c5..db04443bdc3 100644 --- a/test/org/jetbrains/eval4j/test/main.kt +++ b/test/org/jetbrains/eval4j/test/main.kt @@ -147,7 +147,7 @@ object REFLECTION_EVAL : Eval { assertNotNull("Class not found: ${fieldDesc.ownerInternalName}", owner) val field = owner!!.findField(fieldDesc) assertNotNull("Field not found: $fieldDesc", field) - val result = field!!.set(null, newValue.obj) + field!!.set(null, newValue.obj) } override fun invokeStaticMethod(methodDesc: MethodDescription, arguments: List): Value { From 47fd67a9b0c61d2d323a57cd21f0c37264aedba8 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 22:55:49 +0400 Subject: [PATCH 33/66] Get/Put instance field supported --- test/org/jetbrains/eval4j/test/TestData.java | 21 ++++++++ test/org/jetbrains/eval4j/test/main.kt | 50 +++++++++++++------- 2 files changed, 55 insertions(+), 16 deletions(-) diff --git a/test/org/jetbrains/eval4j/test/TestData.java b/test/org/jetbrains/eval4j/test/TestData.java index 39edc598bc7..505571333f6 100644 --- a/test/org/jetbrains/eval4j/test/TestData.java +++ b/test/org/jetbrains/eval4j/test/TestData.java @@ -166,4 +166,25 @@ class TestData { return f2 + f1; } + static class C { + int y = 15; + + static C newC() { + return new C(); + } + } + + static int testGetInstanceField() { + return C.newC().y; + } + + static int testPutInstanceField() { + C c = C.newC(); + c.y = 5; + int f1 = c.y; + c.y = 6; + int f2 = c.y; + return f1 + f2; + } + } diff --git a/test/org/jetbrains/eval4j/test/main.kt b/test/org/jetbrains/eval4j/test/main.kt index db04443bdc3..19204065999 100644 --- a/test/org/jetbrains/eval4j/test/main.kt +++ b/test/org/jetbrains/eval4j/test/main.kt @@ -10,7 +10,6 @@ import junit.framework.TestSuite import junit.framework.TestCase import java.lang.reflect.Method import java.lang.reflect.Field -import kotlin.test.assertNotNull fun suite(): TestSuite { val suite = TestSuite() @@ -132,40 +131,59 @@ object REFLECTION_EVAL : Eval { } override fun getStaticField(fieldDesc: FieldDescription): Value { - assertTrue(fieldDesc.isStatic) - val owner = lookup.findClass(fieldDesc.ownerInternalName) - assertNotNull("Class not found: ${fieldDesc.ownerInternalName}", owner) - val field = owner!!.findField(fieldDesc) - assertNotNull("Field not found: $fieldDesc", field) - val result = field!!.get(null) + val result = findStaticField(fieldDesc).get(null) return objectToValue(result, fieldDesc.fieldType) } override fun setStaticField(fieldDesc: FieldDescription, newValue: Value) { + findStaticField(fieldDesc).set(null, newValue.obj) + } + + fun findStaticField(fieldDesc: FieldDescription): Field { assertTrue(fieldDesc.isStatic) - val owner = lookup.findClass(fieldDesc.ownerInternalName) - assertNotNull("Class not found: ${fieldDesc.ownerInternalName}", owner) - val field = owner!!.findField(fieldDesc) + val field = findClass(fieldDesc).findField(fieldDesc) assertNotNull("Field not found: $fieldDesc", field) - field!!.set(null, newValue.obj) + assertTrue("Field is not static: $field", (field!!.getModifiers() and Modifier.STATIC) != 0) + return field } override fun invokeStaticMethod(methodDesc: MethodDescription, arguments: List): Value { assertTrue(methodDesc.isStatic) - val owner = lookup.findClass(methodDesc.ownerInternalName) - assertNotNull("Class not found: ${methodDesc.ownerInternalName}", owner) - val method = owner!!.findMethod(methodDesc) + val method = findClass(methodDesc).findMethod(methodDesc) assertNotNull("Method not found: $methodDesc", method) val result = method!!.invoke(null, *arguments.map { v -> v.obj }.copyToArray()) return objectToValue(result, methodDesc.returnType) } + fun findClass(memberDesc: MemberDescription): Class { + val owner = lookup.findClass(memberDesc.ownerInternalName) + assertNotNull("Class not found: ${memberDesc.ownerInternalName}", owner) + return owner!! + } + override fun getField(instance: Value, fieldDesc: FieldDescription): Value { - throw UnsupportedOperationException() + val obj = instance.obj + val field = findInstanceField(obj, fieldDesc) + + return objectToValue(field.get(obj), fieldDesc.fieldType) } + override fun setField(instance: Value, fieldDesc: FieldDescription, newValue: Value) { - throw UnsupportedOperationException() + val obj = instance.obj + val field = findInstanceField(obj, fieldDesc) + + field.set(obj, newValue.obj) } + + fun findInstanceField(obj: Any?, fieldDesc: FieldDescription): Field { + if (obj == null) throw NullPointerException() + + val _class: Class = obj.javaClass + val field = _class.findField(fieldDesc) + assertNotNull("Field not found: $fieldDesc", field) + return field!! + } + override fun invokeMethod(instance: Value, methodDesc: MethodDescription, arguments: List, invokespecial: Boolean): Value { throw UnsupportedOperationException() } From e8ac8cd7fad373ed1be302315430fd50fdddcab7 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 22:59:44 +0400 Subject: [PATCH 34/66] Invoke instance method supported --- test/org/jetbrains/eval4j/test/TestData.java | 28 +++++++++++++------- test/org/jetbrains/eval4j/test/main.kt | 7 ++++- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/test/org/jetbrains/eval4j/test/TestData.java b/test/org/jetbrains/eval4j/test/TestData.java index 505571333f6..03d662a08c8 100644 --- a/test/org/jetbrains/eval4j/test/TestData.java +++ b/test/org/jetbrains/eval4j/test/TestData.java @@ -98,7 +98,7 @@ class TestData { } } - static int testSimpleFinally() { + static int simpleFinally() { int i = 5; try { return i; @@ -108,7 +108,7 @@ class TestData { } } - static int testSimpleFinallyWithReturn() { + static int simpleFinallyWithReturn() { int i = 5; try { return i; @@ -118,7 +118,7 @@ class TestData { } } - static int testSimpleFinallyWithContinueInLoop() { + static int simpleFinallyWithContinueInLoop() { int i = 5; while (true) { try { @@ -131,7 +131,7 @@ class TestData { } } - static int testSimpleFinallyWithBreakInLoop() { + static int simpleFinallyWithBreakInLoop() { int i = 5; while (true) { try { @@ -144,21 +144,21 @@ class TestData { return i; } - static Object testCall() { + static Object call() { return Integer.valueOf(1); } - static Object testCallWithObject() { + static Object callWithObject() { return String.valueOf("str"); } - static Object testGetStaticField() { + static Object getStaticField() { return String.CASE_INSENSITIVE_ORDER; } static int FIELD = 0; - static int testPutStaticField() { + static int putStaticField() { FIELD = 5; int f1 = FIELD; FIELD = 6; @@ -169,16 +169,20 @@ class TestData { static class C { int y = 15; + int getY() { + return y; + } + static C newC() { return new C(); } } - static int testGetInstanceField() { + static int getInstanceField() { return C.newC().y; } - static int testPutInstanceField() { + static int putInstanceField() { C c = C.newC(); c.y = 5; int f1 = c.y; @@ -187,4 +191,8 @@ class TestData { return f1 + f2; } + static int instanceMethod() { + return C.newC().getY(); + } + } diff --git a/test/org/jetbrains/eval4j/test/main.kt b/test/org/jetbrains/eval4j/test/main.kt index 19204065999..2d4fde5a073 100644 --- a/test/org/jetbrains/eval4j/test/main.kt +++ b/test/org/jetbrains/eval4j/test/main.kt @@ -185,7 +185,12 @@ object REFLECTION_EVAL : Eval { } override fun invokeMethod(instance: Value, methodDesc: MethodDescription, arguments: List, invokespecial: Boolean): Value { - throw UnsupportedOperationException() + // TODO: INVOKESPECIAL + val obj = instance.obj + val method = obj.javaClass.findMethod(methodDesc) + assertNotNull("Method not found: $methodDesc", method) + val result = method!!.invoke(obj, *arguments.map { v -> v.obj }.copyToArray()) + return objectToValue(result, methodDesc.returnType) } } From 3199a8f690e89af1834c7604e703c71d6af5d074 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 6 Oct 2013 23:41:20 +0400 Subject: [PATCH 35/66] Support constructor calls --- src/org/jetbrains/eval4j/interpreter.kt | 2 +- src/org/jetbrains/eval4j/values.kt | 22 ++++++-- test/org/jetbrains/eval4j/test/TestData.java | 13 +++++ test/org/jetbrains/eval4j/test/main.kt | 56 +++++++++++++++++--- 4 files changed, 82 insertions(+), 11 deletions(-) diff --git a/src/org/jetbrains/eval4j/interpreter.kt b/src/org/jetbrains/eval4j/interpreter.kt index 38734a2e413..6395d1ff5a5 100644 --- a/src/org/jetbrains/eval4j/interpreter.kt +++ b/src/org/jetbrains/eval4j/interpreter.kt @@ -98,7 +98,7 @@ class SingleInstructionInterpreter(private val eval: Eval) : Interpreter( } JSR -> LabelValue((insn as JumpInsnNode).label) GETSTATIC -> eval.getStaticField(FieldDescription(insn as FieldInsnNode)) - NEW -> eval.newInstance(Type.getType((insn as TypeInsnNode).desc)) + NEW -> eval.newInstance(Type.getObjectType((insn as TypeInsnNode).desc)) else -> throw UnsupportedByteCodeException("$insn") } } diff --git a/src/org/jetbrains/eval4j/values.kt b/src/org/jetbrains/eval4j/values.kt index c0f93054736..c2e9e348a9c 100644 --- a/src/org/jetbrains/eval4j/values.kt +++ b/src/org/jetbrains/eval4j/values.kt @@ -30,11 +30,11 @@ class NotInitialized(override val asmType: Type): Value { override fun toString() = "NotInitialized: $asmType" } -abstract class AbstractValue( - public val value: V, +abstract class AbstractValueBase( override val asmType: Type ) : Value { override val valid = true + abstract val value: V override fun toString() = "$value: $asmType" @@ -49,11 +49,19 @@ abstract class AbstractValue( } } +abstract class AbstractValue( + override val value: V, + asmType: Type +) : AbstractValueBase(asmType) + class IntValue(value: Int, asmType: Type): AbstractValue(value, asmType) class LongValue(value: Long): AbstractValue(value, Type.LONG_TYPE) class FloatValue(value: Float): AbstractValue(value, Type.FLOAT_TYPE) class DoubleValue(value: Double): AbstractValue(value, Type.DOUBLE_TYPE) class ObjectValue(value: Any?, asmType: Type): AbstractValue(value, asmType) +class NewObjectValue(val _class: Class, asmType: Type): AbstractValueBase(asmType) { + override var value: Any? = null +} class LabelValue(value: LabelNode): AbstractValue(value, Type.VOID_TYPE) @@ -74,4 +82,12 @@ val Value.int: Int get() = (this as IntValue).value val Value.long: Long get() = (this as LongValue).value val Value.float: Float get() = (this as FloatValue).value val Value.double: Double get() = (this as DoubleValue).value -val Value.obj: Any? get() = (this as AbstractValue<*>).value \ No newline at end of file +val Value.obj: Any? + get(): Any? { + if (this is NewObjectValue) { + val v = value + if (v == null) throw IllegalStateException("Trying to access an unitialized object: $this") + return v + } + return (this as AbstractValue<*>).value + } \ No newline at end of file diff --git a/test/org/jetbrains/eval4j/test/TestData.java b/test/org/jetbrains/eval4j/test/TestData.java index 03d662a08c8..88d890325ce 100644 --- a/test/org/jetbrains/eval4j/test/TestData.java +++ b/test/org/jetbrains/eval4j/test/TestData.java @@ -169,6 +169,12 @@ class TestData { static class C { int y = 15; + C(int y) { + this.y = y; + } + + C() {} + int getY() { return y; } @@ -195,4 +201,11 @@ class TestData { return C.newC().getY(); } + static int constructorCallNoArgs() { + return new C().y; + } + + static int constructorCallWithArgs() { + return new C(10).y; + } } diff --git a/test/org/jetbrains/eval4j/test/main.kt b/test/org/jetbrains/eval4j/test/main.kt index 2d4fde5a073..5fce03afe39 100644 --- a/test/org/jetbrains/eval4j/test/main.kt +++ b/test/org/jetbrains/eval4j/test/main.kt @@ -10,6 +10,7 @@ import junit.framework.TestSuite import junit.framework.TestCase import java.lang.reflect.Method import java.lang.reflect.Field +import java.lang.reflect.Constructor fun suite(): TestSuite { val suite = TestSuite() @@ -102,11 +103,14 @@ object REFLECTION_EVAL : Eval { override fun loadClass(classType: Type): Value { throw UnsupportedOperationException() } + override fun loadString(str: String): Value = ObjectValue(str, Type.getType(javaClass())) override fun newInstance(classType: Type): Value { - throw UnsupportedOperationException() + val _class = findClass(classType.getInternalName()) + return NewObjectValue(_class, classType) } + override fun checkCast(value: Value, targetType: Type): Value { throw UnsupportedOperationException() } @@ -155,9 +159,11 @@ object REFLECTION_EVAL : Eval { return objectToValue(result, methodDesc.returnType) } - fun findClass(memberDesc: MemberDescription): Class { - val owner = lookup.findClass(memberDesc.ownerInternalName) - assertNotNull("Class not found: ${memberDesc.ownerInternalName}", owner) + fun findClass(memberDesc: MemberDescription): Class = findClass(memberDesc.ownerInternalName) + + fun findClass(internalName: String): Class { + val owner = lookup.findClass(internalName) + assertNotNull("Class not found: ${internalName}", owner) return owner!! } @@ -185,7 +191,22 @@ object REFLECTION_EVAL : Eval { } override fun invokeMethod(instance: Value, methodDesc: MethodDescription, arguments: List, invokespecial: Boolean): Value { - // TODO: INVOKESPECIAL + if (invokespecial) { + if (methodDesc.name == "") { + // Constructor call + val _class = (instance as NewObjectValue)._class + val ctor = _class.findConstructor(methodDesc) + assertNotNull("Constructor not found: $methodDesc", ctor) + val args = arguments.map { v -> v.obj }.copyToArray() + val result = ctor!!.newInstance(*args) + instance.value = result + return objectToValue(result, instance.asmType) + } + else { + // TODO + throw UnsupportedOperationException("invokespecial is not suported yet") + } + } val obj = instance.obj val method = obj.javaClass.findMethod(methodDesc) assertNotNull("Method not found: $methodDesc", method) @@ -216,6 +237,24 @@ fun Class.findMethod(methodDesc: MethodDescription): Method? { return null } +[suppress("UNCHECKED_CAST")] +fun Class.findConstructor(methodDesc: MethodDescription): Constructor? { + for (declared in getDeclaredConstructors()) { + if (methodDesc.matches(declared)) return declared as Constructor + } + return null +} + +fun MethodDescription.matches(ctor: Constructor<*>): Boolean { + val methodParams = ctor.getParameterTypes()!! + if (parameterTypes.size() != methodParams.size) return false + for ((i, p) in parameterTypes.withIndices()) { + if (!p.matches(methodParams[i])) return false + } + + return true; +} + fun MethodDescription.matches(method: Method): Boolean { if (name != method.getName()) return false @@ -234,8 +273,11 @@ fun Class.findField(fieldDesc: FieldDescription): Field? { if (fieldDesc.matches(declared)) return declared } - val fromSuperClass = (getSuperclass() as Class).findField(fieldDesc) - if (fromSuperClass != null) return fromSuperClass + val superclass = getSuperclass() + if (superclass != null) { + val fromSuperClass = (superclass as Class).findField(fieldDesc) + if (fromSuperClass != null) return fromSuperClass + } for (supertype in getInterfaces()) { val fromSuper = (supertype as Class).findField(fieldDesc) From 58ad84d4e92aabafbf3e0a57b55f1a45eb42b0e7 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Mon, 7 Oct 2013 00:16:09 +0400 Subject: [PATCH 36/66] Support try..catch and instanceof/casts --- .../org/objectweb/asm/tree/annotations.xml | 9 ++++ src/org/jetbrains/eval4j/interpreter.kt | 15 +++++-- src/org/jetbrains/eval4j/interpreterLoop.kt | 44 +++++++++++++++++-- test/org/jetbrains/eval4j/test/TestData.java | 40 +++++++++++++++++ test/org/jetbrains/eval4j/test/main.kt | 8 ++-- 5 files changed, 103 insertions(+), 13 deletions(-) diff --git a/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml index 7866c5dc168..d2f989a9d19 100644 --- a/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml +++ b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml @@ -386,6 +386,9 @@ + + + @@ -551,6 +554,12 @@ + + + + + + diff --git a/src/org/jetbrains/eval4j/interpreter.kt b/src/org/jetbrains/eval4j/interpreter.kt index 6395d1ff5a5..0d249e2edb8 100644 --- a/src/org/jetbrains/eval4j/interpreter.kt +++ b/src/org/jetbrains/eval4j/interpreter.kt @@ -20,8 +20,7 @@ trait Eval { fun loadClass(classType: Type): Value fun loadString(str: String): Value fun newInstance(classType: Type): Value - fun checkCast(value: Value, targetType: Type): Value - fun isInsetanceOf(value: Value, targetType: Type): Boolean + fun isInstanceOf(value: Value, targetType: Type): Boolean fun newArray(arrayType: Type, size: Int): Value fun newMultiDimensionalArray(arrayType: Type, dimensionSizes: List): Value @@ -176,12 +175,20 @@ class SingleInstructionInterpreter(private val eval: Eval) : Interpreter( CHECKCAST -> { val targetType = Type.getObjectType((insn as TypeInsnNode).desc) - eval.checkCast(value, targetType) + if (eval.isInstanceOf(value, targetType)) { + ObjectValue(value.obj, targetType) + } + else { + throw ThrownFromEvalException(ObjectValue( + ClassCastException("Value '$value' cannot be cast to $targetType"), + Type.getType(javaClass()) + )) + } } INSTANCEOF -> { val targetType = Type.getObjectType((insn as TypeInsnNode).desc) - boolean(eval.isInsetanceOf(value, targetType)) + boolean(eval.isInstanceOf(value, targetType)) } // TODO: maybe just do nothing? diff --git a/src/org/jetbrains/eval4j/interpreterLoop.kt b/src/org/jetbrains/eval4j/interpreterLoop.kt index 6762f9c4501..e0656f436ef 100644 --- a/src/org/jetbrains/eval4j/interpreterLoop.kt +++ b/src/org/jetbrains/eval4j/interpreterLoop.kt @@ -9,6 +9,8 @@ import org.objectweb.asm.tree.analysis.Interpreter import org.objectweb.asm.tree.JumpInsnNode import org.objectweb.asm.tree.VarInsnNode import org.objectweb.asm.util.Printer +import org.objectweb.asm.tree.TryCatchBlockNode +import java.util.ArrayList trait InterpreterResult { fun toString(): String @@ -62,11 +64,27 @@ fun interpreterLoop( } val interpreter = SingleInstructionInterpreter(eval) - var frame = initFrame(ownerClassInternalName, m, interpreter) + val frame = initFrame(ownerClassInternalName, m, interpreter) + val handlers = computeHandlers(m) + + fun exceptionCaught(exceptionValue: Value): Boolean { + val catchBlocks = handlers[m.instructions.indexOf(currentInsn)] ?: listOf() + for (catch in catchBlocks) { + val exceptionTypeDesc = catch.`type` + if (exceptionTypeDesc != null) { + val exceptionType = Type.getType(exceptionTypeDesc) + if (eval.isInstanceOf(exceptionValue, exceptionType)) { + frame.clearStack() + frame.push(exceptionValue) + goto(catch.handler) + return true + } + } + } + return false + } while (true) { - // TODO try-catch-finally support - val insnOpcode = currentInsn.getOpcode() val insnType = currentInsn.getType() @@ -134,11 +152,11 @@ fun interpreterLoop( } } - // TODO: try/catch/finally ATHROW -> { val exceptionValue = frame.getStack(0)!! val handled = handler.exceptionThrown(frame, currentInsn, exceptionValue) if (handled != null) return handled + if (exceptionCaught(exceptionValue)) continue return ExceptionThrown(exceptionValue) } @@ -153,6 +171,7 @@ fun interpreterLoop( // TODO: try/catch.finaly val handled = handler.exceptionThrown(frame, currentInsn, e.exception) if (handled != null) return handled + if (exceptionCaught(e.exception)) continue return ExceptionThrown(e.exception) } } @@ -193,4 +212,21 @@ fun initFrame( } return current +} + +fun computeHandlers(m: MethodNode): Array?> { + val insns = m.instructions + val handlers = Array?>(insns.size()) {null} + for (tcb in m.tryCatchBlocks) { + val begin = insns.indexOf(tcb.start) + val end = insns.indexOf(tcb.end) + for (i in begin..end - 1) { + val insnHandlers = handlers[i] ?: ArrayList() + handlers[i] = insnHandlers + + insnHandlers.add(tcb) + } + } + + return handlers } \ No newline at end of file diff --git a/test/org/jetbrains/eval4j/test/TestData.java b/test/org/jetbrains/eval4j/test/TestData.java index 88d890325ce..16afc02f18e 100644 --- a/test/org/jetbrains/eval4j/test/TestData.java +++ b/test/org/jetbrains/eval4j/test/TestData.java @@ -208,4 +208,44 @@ class TestData { static int constructorCallWithArgs() { return new C(10).y; } + + static class MyEx extends RuntimeException { + final int x; + + MyEx(int x) { + this.x = x; + } + } + + static int tryCatch() { + try { + throw new MyEx(10); + } + catch (MyEx e) { + return e.x; + } + } + + static int tryWiderCatch() { + int a = 10; + try { + if (a > 0) { + throw new MyEx(10); + } + } catch (Exception e) { + return ((MyEx) e).x; + } + return 2; + } + + static int classCastException() { + Object a = ""; + try { + Integer s = (Integer) a; + } + catch (ClassCastException e) { + return 1; + } + return 2; + } } diff --git a/test/org/jetbrains/eval4j/test/main.kt b/test/org/jetbrains/eval4j/test/main.kt index 5fce03afe39..5e60db79c9e 100644 --- a/test/org/jetbrains/eval4j/test/main.kt +++ b/test/org/jetbrains/eval4j/test/main.kt @@ -111,11 +111,9 @@ object REFLECTION_EVAL : Eval { return NewObjectValue(_class, classType) } - override fun checkCast(value: Value, targetType: Type): Value { - throw UnsupportedOperationException() - } - override fun isInsetanceOf(value: Value, targetType: Type): Boolean { - throw UnsupportedOperationException() + override fun isInstanceOf(value: Value, targetType: Type): Boolean { + val _class = findClass(targetType.getInternalName()) + return _class.isInstance(value.obj) } override fun newArray(arrayType: Type, size: Int): Value { From 4a7fa6632e7b9b3b37cdd4417d74119dbaec5218 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Mon, 7 Oct 2013 00:17:52 +0400 Subject: [PATCH 37/66] Class literals supported --- test/org/jetbrains/eval4j/test/TestData.java | 4 ++++ test/org/jetbrains/eval4j/test/main.kt | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/test/org/jetbrains/eval4j/test/TestData.java b/test/org/jetbrains/eval4j/test/TestData.java index 16afc02f18e..db22febb1bc 100644 --- a/test/org/jetbrains/eval4j/test/TestData.java +++ b/test/org/jetbrains/eval4j/test/TestData.java @@ -248,4 +248,8 @@ class TestData { } return 2; } + + static String classLiteral() { + return String.class.toString(); + } } diff --git a/test/org/jetbrains/eval4j/test/main.kt b/test/org/jetbrains/eval4j/test/main.kt index 5e60db79c9e..32010f02372 100644 --- a/test/org/jetbrains/eval4j/test/main.kt +++ b/test/org/jetbrains/eval4j/test/main.kt @@ -101,7 +101,7 @@ object REFLECTION_EVAL : Eval { val lookup = ReflectionLookup(javaClass().getClassLoader()!!) override fun loadClass(classType: Type): Value { - throw UnsupportedOperationException() + return ObjectValue(findClass(classType.getInternalName()), Type.getType(javaClass>())) } override fun loadString(str: String): Value = ObjectValue(str, Type.getType(javaClass())) From 1dfbbb9f084dbb7c1b1fa07c5157d41159f2383e Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Mon, 7 Oct 2013 00:32:34 +0400 Subject: [PATCH 38/66] Support NPE and exceptions thrown from methods being called --- src/org/jetbrains/eval4j/values.kt | 2 +- test/org/jetbrains/eval4j/test/TestData.java | 25 +++++++ test/org/jetbrains/eval4j/test/main.kt | 78 +++++++++++++------- 3 files changed, 79 insertions(+), 26 deletions(-) diff --git a/src/org/jetbrains/eval4j/values.kt b/src/org/jetbrains/eval4j/values.kt index c2e9e348a9c..0974356d0d3 100644 --- a/src/org/jetbrains/eval4j/values.kt +++ b/src/org/jetbrains/eval4j/values.kt @@ -59,7 +59,7 @@ class LongValue(value: Long): AbstractValue(value, Type.LONG_TYPE) class FloatValue(value: Float): AbstractValue(value, Type.FLOAT_TYPE) class DoubleValue(value: Double): AbstractValue(value, Type.DOUBLE_TYPE) class ObjectValue(value: Any?, asmType: Type): AbstractValue(value, asmType) -class NewObjectValue(val _class: Class, asmType: Type): AbstractValueBase(asmType) { +class NewObjectValue(val _class: Class<*>, asmType: Type): AbstractValueBase(asmType) { override var value: Any? = null } diff --git a/test/org/jetbrains/eval4j/test/TestData.java b/test/org/jetbrains/eval4j/test/TestData.java index db22febb1bc..239df0dc5e4 100644 --- a/test/org/jetbrains/eval4j/test/TestData.java +++ b/test/org/jetbrains/eval4j/test/TestData.java @@ -182,6 +182,10 @@ class TestData { static C newC() { return new C(); } + + static void throwException() { + throw new RuntimeException(); + } } static int getInstanceField() { @@ -252,4 +256,25 @@ class TestData { static String classLiteral() { return String.class.toString(); } + + static int callThrowingMethod() { + try { + C.throwException(); + } + catch (RuntimeException e) { + return 1; + } + return 0; + } + + static int NPE() { + try { + Object x = null; + x.toString(); + } + catch (NullPointerException e) { + return 1; + } + return 0; + } } diff --git a/test/org/jetbrains/eval4j/test/main.kt b/test/org/jetbrains/eval4j/test/main.kt index 32010f02372..9485f0bd30a 100644 --- a/test/org/jetbrains/eval4j/test/main.kt +++ b/test/org/jetbrains/eval4j/test/main.kt @@ -11,6 +11,7 @@ import junit.framework.TestCase import java.lang.reflect.Method import java.lang.reflect.Field import java.lang.reflect.Constructor +import java.lang.reflect.InvocationTargetException fun suite(): TestSuite { val suite = TestSuite() @@ -132,13 +133,38 @@ object REFLECTION_EVAL : Eval { throw UnsupportedOperationException() } + fun mayThrow(f: () -> T): T { + try { + try { + return f() + } + catch (ite: InvocationTargetException) { + throw ite.getCause() ?: ite + } + } + catch (e: Throwable) { + throw ThrownFromEvalException(ObjectValue(e, Type.getType(e.javaClass))) + } + } + + fun Any?.checkNull(): Any { + if (this == null) { + throw ThrownFromEvalException(ObjectValue(NullPointerException(), Type.getType(javaClass()))) + } + return this + } + override fun getStaticField(fieldDesc: FieldDescription): Value { - val result = findStaticField(fieldDesc).get(null) + val field = findStaticField(fieldDesc) + + val result = mayThrow {field.get(null)} return objectToValue(result, fieldDesc.fieldType) } override fun setStaticField(fieldDesc: FieldDescription, newValue: Value) { - findStaticField(fieldDesc).set(null, newValue.obj) + val field = findStaticField(fieldDesc) + val obj = newValue.obj + mayThrow {field.set(null, obj)} } fun findStaticField(fieldDesc: FieldDescription): Field { @@ -153,36 +179,36 @@ object REFLECTION_EVAL : Eval { assertTrue(methodDesc.isStatic) val method = findClass(methodDesc).findMethod(methodDesc) assertNotNull("Method not found: $methodDesc", method) - val result = method!!.invoke(null, *arguments.map { v -> v.obj }.copyToArray()) + val args = arguments.map { v -> v.obj }.copyToArray() + val result = mayThrow {method!!.invoke(null, *args)} return objectToValue(result, methodDesc.returnType) } - fun findClass(memberDesc: MemberDescription): Class = findClass(memberDesc.ownerInternalName) + fun findClass(memberDesc: MemberDescription): Class = findClass(memberDesc.ownerInternalName) - fun findClass(internalName: String): Class { + fun findClass(internalName: String): Class { val owner = lookup.findClass(internalName) assertNotNull("Class not found: ${internalName}", owner) return owner!! } override fun getField(instance: Value, fieldDesc: FieldDescription): Value { - val obj = instance.obj + val obj = instance.obj.checkNull() val field = findInstanceField(obj, fieldDesc) - return objectToValue(field.get(obj), fieldDesc.fieldType) + return objectToValue(mayThrow {field.get(obj)}, fieldDesc.fieldType) } override fun setField(instance: Value, fieldDesc: FieldDescription, newValue: Value) { - val obj = instance.obj + val obj = instance.obj.checkNull() val field = findInstanceField(obj, fieldDesc) - field.set(obj, newValue.obj) + val newObj = newValue.obj + mayThrow {field.set(obj, newObj)} } - fun findInstanceField(obj: Any?, fieldDesc: FieldDescription): Field { - if (obj == null) throw NullPointerException() - - val _class: Class = obj.javaClass + fun findInstanceField(obj: Any, fieldDesc: FieldDescription): Field { + val _class = obj.javaClass val field = _class.findField(fieldDesc) assertNotNull("Field not found: $fieldDesc", field) return field!! @@ -192,11 +218,12 @@ object REFLECTION_EVAL : Eval { if (invokespecial) { if (methodDesc.name == "") { // Constructor call - val _class = (instance as NewObjectValue)._class + [suppress("UNCHECKED_CAST")] + val _class = (instance as NewObjectValue)._class as Class val ctor = _class.findConstructor(methodDesc) assertNotNull("Constructor not found: $methodDesc", ctor) val args = arguments.map { v -> v.obj }.copyToArray() - val result = ctor!!.newInstance(*args) + val result = mayThrow {ctor!!.newInstance(*args)} instance.value = result return objectToValue(result, instance.asmType) } @@ -205,30 +232,31 @@ object REFLECTION_EVAL : Eval { throw UnsupportedOperationException("invokespecial is not suported yet") } } - val obj = instance.obj + val obj = instance.obj.checkNull() val method = obj.javaClass.findMethod(methodDesc) assertNotNull("Method not found: $methodDesc", method) - val result = method!!.invoke(obj, *arguments.map { v -> v.obj }.copyToArray()) + val args = arguments.map { v -> v.obj }.copyToArray() + val result = mayThrow {method!!.invoke(obj, *args)} return objectToValue(result, methodDesc.returnType) } } class ReflectionLookup(val classLoader: ClassLoader) { [suppress("UNCHECKED_CAST")] - fun findClass(internalName: String): Class? = classLoader.loadClass(internalName.replace('/', '.')) as Class + fun findClass(internalName: String): Class? = classLoader.loadClass(internalName.replace('/', '.')) as Class } [suppress("UNCHECKED_CAST")] -fun Class.findMethod(methodDesc: MethodDescription): Method? { +fun Class.findMethod(methodDesc: MethodDescription): Method? { for (declared in getDeclaredMethods()) { if (methodDesc.matches(declared)) return declared } - val fromSuperClass = (getSuperclass() as Class).findMethod(methodDesc) + val fromSuperClass = (getSuperclass() as Class).findMethod(methodDesc) if (fromSuperClass != null) return fromSuperClass for (supertype in getInterfaces()) { - val fromSuper = (supertype as Class).findMethod(methodDesc) + val fromSuper = (supertype as Class).findMethod(methodDesc) if (fromSuper != null) return fromSuper } @@ -236,7 +264,7 @@ fun Class.findMethod(methodDesc: MethodDescription): Method? { } [suppress("UNCHECKED_CAST")] -fun Class.findConstructor(methodDesc: MethodDescription): Constructor? { +fun Class.findConstructor(methodDesc: MethodDescription): Constructor? { for (declared in getDeclaredConstructors()) { if (methodDesc.matches(declared)) return declared as Constructor } @@ -266,19 +294,19 @@ fun MethodDescription.matches(method: Method): Boolean { } [suppress("UNCHECKED_CAST")] -fun Class.findField(fieldDesc: FieldDescription): Field? { +fun Class.findField(fieldDesc: FieldDescription): Field? { for (declared in getDeclaredFields()) { if (fieldDesc.matches(declared)) return declared } val superclass = getSuperclass() if (superclass != null) { - val fromSuperClass = (superclass as Class).findField(fieldDesc) + val fromSuperClass = (superclass as Class).findField(fieldDesc) if (fromSuperClass != null) return fromSuperClass } for (supertype in getInterfaces()) { - val fromSuper = (supertype as Class).findField(fieldDesc) + val fromSuper = (supertype as Class).findField(fieldDesc) if (fromSuper != null) return fromSuper } From e1e1428f4b0af6eff83db74ca4bff7604441b18f Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Mon, 7 Oct 2013 01:44:29 +0400 Subject: [PATCH 39/66] Exception handling fixed for the case of inner classes --- src/org/jetbrains/eval4j/interpreterLoop.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/org/jetbrains/eval4j/interpreterLoop.kt b/src/org/jetbrains/eval4j/interpreterLoop.kt index e0656f436ef..b75a0b1002a 100644 --- a/src/org/jetbrains/eval4j/interpreterLoop.kt +++ b/src/org/jetbrains/eval4j/interpreterLoop.kt @@ -70,9 +70,9 @@ fun interpreterLoop( fun exceptionCaught(exceptionValue: Value): Boolean { val catchBlocks = handlers[m.instructions.indexOf(currentInsn)] ?: listOf() for (catch in catchBlocks) { - val exceptionTypeDesc = catch.`type` - if (exceptionTypeDesc != null) { - val exceptionType = Type.getType(exceptionTypeDesc) + val exceptionTypeInternalName = catch.`type` + if (exceptionTypeInternalName != null) { + val exceptionType = Type.getObjectType(exceptionTypeInternalName) if (eval.isInstanceOf(exceptionValue, exceptionType)) { frame.clearStack() frame.push(exceptionValue) From 00fd00e09b7b0ceb05927e9e98f63d6f1f75d6e5 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Mon, 7 Oct 2013 01:45:05 +0400 Subject: [PATCH 40/66] Arrays supported --- .../org/objectweb/asm/annotations.xml | 817 +++++++++--------- .../jetbrains/eval4j/test/ArrayHelper.java | 16 + test/org/jetbrains/eval4j/test/TestData.java | 180 ++++ test/org/jetbrains/eval4j/test/main.kt | 88 +- 4 files changed, 680 insertions(+), 421 deletions(-) create mode 100644 test/org/jetbrains/eval4j/test/ArrayHelper.java diff --git a/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/annotations.xml b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/annotations.xml index cba4afb9059..c608a4842d3 100644 --- a/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/annotations.xml +++ b/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/annotations.xml @@ -1,272 +1,74 @@ - - - - + - + - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - - - - + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + - + - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -278,172 +80,25 @@ - + - + - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -455,68 +110,215 @@ - + - - - - - - - + - + - + - + - + - + - + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + - + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -524,8 +326,11 @@ - + + + + @@ -533,19 +338,217 @@ - - - - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/org/jetbrains/eval4j/test/ArrayHelper.java b/test/org/jetbrains/eval4j/test/ArrayHelper.java new file mode 100644 index 00000000000..cf816d4ebf4 --- /dev/null +++ b/test/org/jetbrains/eval4j/test/ArrayHelper.java @@ -0,0 +1,16 @@ +package org.jetbrains.eval4j.test; + +import java.lang.reflect.Array; + +public class ArrayHelper { + public static Object newMultiArray(Class elementType, Integer... dimensions) { + int[] dims = new int[dimensions.length]; + int i = 0; + for (Integer dimension : dimensions) { + dims[i] = dimension; + i++; + } + + return Array.newInstance(elementType, dims); + } +} diff --git a/test/org/jetbrains/eval4j/test/TestData.java b/test/org/jetbrains/eval4j/test/TestData.java index 239df0dc5e4..95878595309 100644 --- a/test/org/jetbrains/eval4j/test/TestData.java +++ b/test/org/jetbrains/eval4j/test/TestData.java @@ -277,4 +277,184 @@ class TestData { } return 0; } + + static Class arrayClass() { + return int[].class; + } + + static int arrayOfByte() { + byte[] a = new byte[] {1, 2, 3}; + int sum = 0; + for (int i : a) { + sum += i; + } + return sum; + } + + static int arrayOfShort() { + short[] a = new short[] {1, 2, 3}; + int sum = 0; + for (int i : a) { + sum += i; + } + return sum; + } + + static int arrayOfChar() { + char[] a = new char[] {1, 2, 3}; + int sum = 0; + for (int i : a) { + sum += i; + } + return sum; + } + + static int arrayOfInt() { + int[] a = new int[] {1, 2, 3}; + int sum = 0; + for (int i : a) { + sum += i; + } + return sum; + } + + static int arrayOfLong() { + long[] a = new long[] {1, 2, 3}; + int sum = 0; + for (long i : a) { + sum += i; + } + return sum; + } + + static float arrayOfFloat() { + float[] a = new float[] {1, 2, 3}; + float sum = 0; + for (float i : a) { + sum += i; + } + return sum; + } + + static double arrayOfDouble() { + double[] a = new double[] {1, 2, 3}; + double sum = 0; + for (double i : a) { + sum += i; + } + return sum; + } + + static String arrayOfString() { + String[] a = new String[] {"1", "2", "3"}; + String sum = ""; + for (String i : a) { + sum += i; + } + return sum; + } + + static int arrayOfByte2() { + byte[][] a = new byte[][] {{1}, {2}, {3}}; + int sum = 0; + for (byte[] aa: a) + for (int i : aa) { + sum += i; + } + return sum; + } + + static int arrayOfShort2() { + short[][] a = new short[][] {{1}, {2}, {3}}; + int sum = 0; + for (short[] aa: a) + for (int i : aa) { + sum += i; + } + return sum; + } + + static int arrayOfChar2() { + char[][] a = new char[][] {{1}, {2}, {3}}; + int sum = 0; + for (char[] aa: a) + for (int i : aa) { + sum += i; + } + return sum; + } + + static int arrayOfInt2() { + int[][] a = new int[][] {{1}, {2}, {3}}; + int sum = 0; + for (int[] aa: a) + for (int i : aa) { + sum += i; + } + return sum; + } + + static int arrayOfLong2() { + long[][] a = new long[][] {{1}, {2}, {3}}; + int sum = 0; + for (long[] aa: a) + for (long i : aa) { + sum += i; + } + return sum; + } + + static float arrayOfFloat2() { + float[][] a = new float[][] {{1}, {2}, {3}}; + float sum = 0; + for (float[] aa: a) + for (float i : aa) { + sum += i; + } + return sum; + } + + static double arrayOfDouble2() { + double[][] a = new double[][] {{1}, {2}, {3}}; + double sum = 0; + for (double[] aa: a) + for (double i : aa) { + sum += i; + } + return sum; + } + + static String arrayOfString2() { + String[][] a = new String[][] {{"1"}, {"2"}, {"3"}}; + String sum = ""; + for (String[] aa: a) + for (String i : aa) { + sum += i; + } + return sum; + } + + static String multiArrayOfInt() { + int[][] a = new int[2][2]; + String s = ""; + for (int[] x : a) + for (int y : x) { + s += y; + } + return s; + } + + static String multiArrayOfString() { + String[][] a = new String[2][2]; + for (String[] x : a) + for (int i = 0; i < x.length; i++) { + x[i] = i + ""; + } + String s = ""; + for (String[] x : a) + for (String y : x) { + s += y; + } + return s; + } } diff --git a/test/org/jetbrains/eval4j/test/main.kt b/test/org/jetbrains/eval4j/test/main.kt index 9485f0bd30a..89f54af4626 100644 --- a/test/org/jetbrains/eval4j/test/main.kt +++ b/test/org/jetbrains/eval4j/test/main.kt @@ -12,6 +12,7 @@ import java.lang.reflect.Method import java.lang.reflect.Field import java.lang.reflect.Constructor import java.lang.reflect.InvocationTargetException +import java.lang.reflect.Array as JArray fun suite(): TestSuite { val suite = TestSuite() @@ -102,35 +103,80 @@ object REFLECTION_EVAL : Eval { val lookup = ReflectionLookup(javaClass().getClassLoader()!!) override fun loadClass(classType: Type): Value { - return ObjectValue(findClass(classType.getInternalName()), Type.getType(javaClass>())) + return ObjectValue(findClass(classType), Type.getType(javaClass>())) } override fun loadString(str: String): Value = ObjectValue(str, Type.getType(javaClass())) override fun newInstance(classType: Type): Value { - val _class = findClass(classType.getInternalName()) + val _class = findClass(classType) return NewObjectValue(_class, classType) } override fun isInstanceOf(value: Value, targetType: Type): Boolean { - val _class = findClass(targetType.getInternalName()) + val _class = findClass(targetType) return _class.isInstance(value.obj) } + [suppress("UNCHECKED_CAST")] override fun newArray(arrayType: Type, size: Int): Value { - throw UnsupportedOperationException() + return ObjectValue(JArray.newInstance(findClass(arrayType).getComponentType() as Class, size), arrayType) } + override fun newMultiDimensionalArray(arrayType: Type, dimensionSizes: List): Value { - throw UnsupportedOperationException() + return ObjectValue(ArrayHelper.newMultiArray(findClass(arrayType.getElementType()), *dimensionSizes.copyToArray()), arrayType) } + override fun getArrayLength(array: Value): Value { - throw UnsupportedOperationException() + return int(JArray.getLength(array.obj.checkNull())) } + override fun getArrayElement(array: Value, index: Value): Value { - throw UnsupportedOperationException() + val asmType = array.asmType + val elementType = if (asmType.getDimensions() == 1) asmType.getElementType() else Type.getType(asmType.getDescriptor().substring(1)) + val arr = array.obj.checkNull() + val ind = index.int + return when (elementType.getSort()) { + Type.BOOLEAN -> boolean(JArray.getBoolean(arr, ind)) + Type.BYTE -> byte(JArray.getByte(arr, ind)) + Type.SHORT -> short(JArray.getShort(arr, ind)) + Type.CHAR -> char(JArray.getChar(arr, ind)) + Type.INT -> int(JArray.getInt(arr, ind)) + Type.LONG -> long(JArray.getLong(arr, ind)) + Type.FLOAT -> float(JArray.getFloat(arr, ind)) + Type.DOUBLE -> double(JArray.getDouble(arr, ind)) + Type.OBJECT, + Type.ARRAY -> { + val value = JArray.get(arr, ind) + if (value == null) NULL_VALUE else ObjectValue(value, Type.getType(value.javaClass)) + } + else -> throw UnsupportedOperationException("Unsupported array element type: $elementType") + } } + override fun setArrayElement(array: Value, index: Value, newValue: Value) { - throw UnsupportedOperationException() + val arr = array.obj.checkNull() + val ind = index.int + if (array.asmType.getDimensions() > 1) { + JArray.set(arr, ind, newValue.obj) + return + } + val elementType = array.asmType.getElementType() + when (elementType.getSort()) { + Type.BOOLEAN -> JArray.setBoolean(arr, ind, newValue.boolean) + Type.BYTE -> JArray.setByte(arr, ind, newValue.int.toByte()) + Type.SHORT -> JArray.setShort(arr, ind, newValue.int.toShort()) + Type.CHAR -> JArray.setChar(arr, ind, newValue.int.toChar()) + Type.INT -> JArray.setInt(arr, ind, newValue.int) + Type.LONG -> JArray.setLong(arr, ind, newValue.long) + Type.FLOAT -> JArray.setFloat(arr, ind, newValue.float) + Type.DOUBLE -> JArray.setDouble(arr, ind, newValue.double) + Type.OBJECT, + Type.ARRAY -> { + JArray.set(arr, ind, newValue.obj) + } + else -> throw UnsupportedOperationException("Unsupported array element type: $elementType") + } } fun mayThrow(f: () -> T): T { @@ -184,12 +230,12 @@ object REFLECTION_EVAL : Eval { return objectToValue(result, methodDesc.returnType) } - fun findClass(memberDesc: MemberDescription): Class = findClass(memberDesc.ownerInternalName) + fun findClass(memberDesc: MemberDescription): Class = findClass(Type.getObjectType(memberDesc.ownerInternalName)) - fun findClass(internalName: String): Class { - val owner = lookup.findClass(internalName) - assertNotNull("Class not found: ${internalName}", owner) - return owner!! + fun findClass(asmType: Type): Class { + val owner = lookup.findClass(asmType) + assertNotNull("Class not found: ${asmType}", owner) + return owner as Class } override fun getField(instance: Value, fieldDesc: FieldDescription): Value { @@ -243,7 +289,21 @@ object REFLECTION_EVAL : Eval { class ReflectionLookup(val classLoader: ClassLoader) { [suppress("UNCHECKED_CAST")] - fun findClass(internalName: String): Class? = classLoader.loadClass(internalName.replace('/', '.')) as Class + fun findClass(asmType: Type): Class<*>? { + return when (asmType.getSort()) { + Type.BOOLEAN -> java.lang.Boolean.TYPE + Type.BYTE -> java.lang.Byte.TYPE + Type.SHORT -> java.lang.Short.TYPE + Type.CHAR -> java.lang.Character.TYPE + Type.INT -> java.lang.Integer.TYPE + Type.LONG -> java.lang.Long.TYPE + Type.FLOAT -> java.lang.Float.TYPE + Type.DOUBLE -> java.lang.Double.TYPE + Type.OBJECT -> classLoader.loadClass(asmType.getInternalName().replace('/', '.')) + Type.ARRAY -> Class.forName(asmType.getDescriptor().replace('/', '.')) + else -> throw UnsupportedOperationException("Unsupported type: $asmType") + } + } } [suppress("UNCHECKED_CAST")] From 2e8ae6b7db2a2993341ebb97a1785e6f353922ac Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Mon, 7 Oct 2013 01:45:51 +0400 Subject: [PATCH 41/66] TODO removed --- src/org/jetbrains/eval4j/interpreter.kt | 1 - src/org/jetbrains/eval4j/interpreterLoop.kt | 1 - 2 files changed, 2 deletions(-) diff --git a/src/org/jetbrains/eval4j/interpreter.kt b/src/org/jetbrains/eval4j/interpreter.kt index 0d249e2edb8..fb8a32e39e0 100644 --- a/src/org/jetbrains/eval4j/interpreter.kt +++ b/src/org/jetbrains/eval4j/interpreter.kt @@ -359,7 +359,6 @@ class SingleInstructionInterpreter(private val eval: Eval) : Interpreter( override fun returnOperation(insn: AbstractInsnNode, value: Value, expected: Value) { when (insn.getOpcode()) { IRETURN, LRETURN, FRETURN, DRETURN, ARETURN -> { - // TODO: coercion, maybe? // Handled by interpreter loop } diff --git a/src/org/jetbrains/eval4j/interpreterLoop.kt b/src/org/jetbrains/eval4j/interpreterLoop.kt index b75a0b1002a..658c36f5805 100644 --- a/src/org/jetbrains/eval4j/interpreterLoop.kt +++ b/src/org/jetbrains/eval4j/interpreterLoop.kt @@ -168,7 +168,6 @@ fun interpreterLoop( frame.execute(currentInsn, interpreter) } catch (e: ThrownFromEvalException) { - // TODO: try/catch.finaly val handled = handler.exceptionThrown(frame, currentInsn, e.exception) if (handled != null) return handled if (exceptionCaught(e.exception)) continue From 41d576c10852641b3563ab4c24e9a166770d4fb8 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sat, 12 Oct 2013 23:40:56 +0400 Subject: [PATCH 42/66] Support exceptionCaught() handler --- src/org/jetbrains/eval4j/interpreterLoop.kt | 171 ++++++++++---------- 1 file changed, 90 insertions(+), 81 deletions(-) diff --git a/src/org/jetbrains/eval4j/interpreterLoop.kt b/src/org/jetbrains/eval4j/interpreterLoop.kt index 658c36f5805..948aaf1ddc5 100644 --- a/src/org/jetbrains/eval4j/interpreterLoop.kt +++ b/src/org/jetbrains/eval4j/interpreterLoop.kt @@ -67,6 +67,8 @@ fun interpreterLoop( val frame = initFrame(ownerClassInternalName, m, interpreter) val handlers = computeHandlers(m) + class ResultException(val result: InterpreterResult): RuntimeException() + fun exceptionCaught(exceptionValue: Value): Boolean { val catchBlocks = handlers[m.instructions.indexOf(currentInsn)] ?: listOf() for (catch in catchBlocks) { @@ -74,6 +76,8 @@ fun interpreterLoop( if (exceptionTypeInternalName != null) { val exceptionType = Type.getObjectType(exceptionTypeInternalName) if (eval.isInstanceOf(exceptionValue, exceptionType)) { + val handled = handler.exceptionCaught(frame, currentInsn, exceptionValue) + if (handled != null) throw ResultException(handled) frame.clearStack() frame.push(exceptionValue) goto(catch.handler) @@ -84,102 +88,107 @@ fun interpreterLoop( return false } - while (true) { - val insnOpcode = currentInsn.getOpcode() - val insnType = currentInsn.getType() + try { + while (true) { + val insnOpcode = currentInsn.getOpcode() + val insnType = currentInsn.getType() - when (insnType) { - AbstractInsnNode.LABEL, - AbstractInsnNode.FRAME, - AbstractInsnNode.LINE -> { - // skip to the next instruction - } + when (insnType) { + AbstractInsnNode.LABEL, + AbstractInsnNode.FRAME, + AbstractInsnNode.LINE -> { + // skip to the next instruction + } - else -> { - when (insnOpcode) { - GOTO -> { - goto((currentInsn as JumpInsnNode).label) - continue - } - - RET -> { - val varNode = currentInsn as VarInsnNode - val address = frame.getLocal(varNode.`var`) - goto((address as LabelValue).value) - continue - } - - // TODO: switch - LOOKUPSWITCH -> UnsupportedByteCodeException("LOOKUPSWITCH is not supported yet") - TABLESWITCH -> UnsupportedByteCodeException("TABLESWITCH is not supported yet") - - IRETURN, LRETURN, FRETURN, DRETURN, ARETURN -> { - val value = frame.getStack(0)!! - val expectedType = Type.getReturnType(m.desc) - if (expectedType.getSort() == Type.OBJECT) { - val coerced = if (value != NULL_VALUE && value.asmType != expectedType) - ObjectValue(value.obj, expectedType) - else value - return ValueReturned(coerced) + else -> { + when (insnOpcode) { + GOTO -> { + goto((currentInsn as JumpInsnNode).label) + continue } - if (value.asmType != expectedType) { - assert(insnOpcode == IRETURN, "Only ints should be coerced: " + Printer.OPCODES[insnOpcode]) - val coerced = when (expectedType.getSort()) { - Type.BOOLEAN -> boolean(value.boolean) - Type.BYTE -> byte(value.int.toByte()) - Type.SHORT -> short(value.int.toShort()) - Type.CHAR -> char(value.int.toChar()) - else -> throw UnsupportedByteCodeException("Should not be coerced: $expectedType") + RET -> { + val varNode = currentInsn as VarInsnNode + val address = frame.getLocal(varNode.`var`) + goto((address as LabelValue).value) + continue + } + + // TODO: switch + LOOKUPSWITCH -> UnsupportedByteCodeException("LOOKUPSWITCH is not supported yet") + TABLESWITCH -> UnsupportedByteCodeException("TABLESWITCH is not supported yet") + + IRETURN, LRETURN, FRETURN, DRETURN, ARETURN -> { + val value = frame.getStack(0)!! + val expectedType = Type.getReturnType(m.desc) + if (expectedType.getSort() == Type.OBJECT) { + val coerced = if (value != NULL_VALUE && value.asmType != expectedType) + ObjectValue(value.obj, expectedType) + else value + return ValueReturned(coerced) } - return ValueReturned(coerced) + if (value.asmType != expectedType) { + assert(insnOpcode == IRETURN, "Only ints should be coerced: " + Printer.OPCODES[insnOpcode]) + + val coerced = when (expectedType.getSort()) { + Type.BOOLEAN -> boolean(value.boolean) + Type.BYTE -> byte(value.int.toByte()) + Type.SHORT -> short(value.int.toShort()) + Type.CHAR -> char(value.int.toChar()) + else -> throw UnsupportedByteCodeException("Should not be coerced: $expectedType") + } + return ValueReturned(coerced) + } + return ValueReturned(value) } - return ValueReturned(value) - } - RETURN -> return ValueReturned(VOID_VALUE) - IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IFNULL, IFNONNULL -> { - if (interpreter.checkUnaryCondition(frame.getStack(0)!!, insnOpcode)) { - frame.execute(currentInsn, interpreter) - goto((currentInsn as JumpInsnNode).label) - continue + RETURN -> return ValueReturned(VOID_VALUE) + IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IFNULL, IFNONNULL -> { + if (interpreter.checkUnaryCondition(frame.getStack(0)!!, insnOpcode)) { + frame.execute(currentInsn, interpreter) + goto((currentInsn as JumpInsnNode).label) + continue + } } - } - IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE -> { - if (interpreter.checkBinaryCondition(frame.getStack(0)!!, frame.getStack(1)!!, insnOpcode)) { - frame.execute(currentInsn, interpreter) - goto((currentInsn as JumpInsnNode).label) - continue + IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE -> { + if (interpreter.checkBinaryCondition(frame.getStack(0)!!, frame.getStack(1)!!, insnOpcode)) { + frame.execute(currentInsn, interpreter) + goto((currentInsn as JumpInsnNode).label) + continue + } } + + ATHROW -> { + val exceptionValue = frame.getStack(0)!! + val handled = handler.exceptionThrown(frame, currentInsn, exceptionValue) + if (handled != null) return handled + if (exceptionCaught(exceptionValue)) continue + return ExceptionThrown(exceptionValue) + } + + // Workaround for a bug in Kotlin: NoPatterMatched exception is thrown otherwise! + else -> {} } - ATHROW -> { - val exceptionValue = frame.getStack(0)!! - val handled = handler.exceptionThrown(frame, currentInsn, exceptionValue) + try { + frame.execute(currentInsn, interpreter) + } + catch (e: ThrownFromEvalException) { + val handled = handler.exceptionThrown(frame, currentInsn, e.exception) if (handled != null) return handled - if (exceptionCaught(exceptionValue)) continue - return ExceptionThrown(exceptionValue) + if (exceptionCaught(e.exception)) continue + return ExceptionThrown(e.exception) } - - // Workaround for a bug in Kotlin: NoPatterMatched exception is thrown otherwise! - else -> {} - } - - try { - frame.execute(currentInsn, interpreter) - } - catch (e: ThrownFromEvalException) { - val handled = handler.exceptionThrown(frame, currentInsn, e.exception) - if (handled != null) return handled - if (exceptionCaught(e.exception)) continue - return ExceptionThrown(e.exception) } } + + val handled = handler.instructionProcessed(currentInsn) + if (handled != null) return handled + + goto(currentInsn.getNext()) } - - val handled = handler.instructionProcessed(currentInsn) - if (handled != null) return handled - - goto(currentInsn.getNext()) + } + catch(e: ResultException) { + return e.result } } From 38e8e6addda1036bd25f0c034c689d2753fdc862 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sat, 12 Oct 2013 23:47:39 +0400 Subject: [PATCH 43/66] Start with initial frame, not from scratch --- src/org/jetbrains/eval4j/interpreterLoop.kt | 33 ++---------------- test/org/jetbrains/eval4j/test/main.kt | 37 ++++++++++++++++++++- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/src/org/jetbrains/eval4j/interpreterLoop.kt b/src/org/jetbrains/eval4j/interpreterLoop.kt index 948aaf1ddc5..6b11505da2a 100644 --- a/src/org/jetbrains/eval4j/interpreterLoop.kt +++ b/src/org/jetbrains/eval4j/interpreterLoop.kt @@ -48,8 +48,8 @@ trait InterpretationEventHandler { class ThrownFromEvalException(val exception: Value): RuntimeException() fun interpreterLoop( - ownerClassInternalName: String, m: MethodNode, + initialState: Frame, eval: Eval, handler: InterpretationEventHandler = InterpretationEventHandler.NONE ): InterpreterResult { @@ -64,7 +64,7 @@ fun interpreterLoop( } val interpreter = SingleInstructionInterpreter(eval) - val frame = initFrame(ownerClassInternalName, m, interpreter) + val frame = Frame(initialState) val handlers = computeHandlers(m) class ResultException(val result: InterpreterResult): RuntimeException() @@ -193,35 +193,6 @@ fun interpreterLoop( } // Copied from org.objectweb.asm.tree.analysis.Analyzer.analyze() -fun initFrame( - owner: String, - m: MethodNode, - interpreter: Interpreter -): Frame { - val current = Frame(m.maxLocals, m.maxStack) - current.setReturn(interpreter.newValue(Type.getReturnType(m.desc))) - - var local = 0 - if ((m.access and ACC_STATIC) == 0) { - val ctype = Type.getObjectType(owner) - current.setLocal(local++, interpreter.newValue(ctype)) - } - - val args = Type.getArgumentTypes(m.desc) - for (i in 0..args.size - 1) { - current.setLocal(local++, interpreter.newValue(args[i])) - if (args[i].getSize() == 2) { - current.setLocal(local++, interpreter.newValue(null)) - } - } - - while (local < m.maxLocals) { - current.setLocal(local++, interpreter.newValue(null)) - } - - return current -} - fun computeHandlers(m: MethodNode): Array?> { val insns = m.instructions val handlers = Array?>(insns.size()) {null} diff --git a/test/org/jetbrains/eval4j/test/main.kt b/test/org/jetbrains/eval4j/test/main.kt index 89f54af4626..0ee8ee76ccc 100644 --- a/test/org/jetbrains/eval4j/test/main.kt +++ b/test/org/jetbrains/eval4j/test/main.kt @@ -13,6 +13,8 @@ import java.lang.reflect.Field import java.lang.reflect.Constructor import java.lang.reflect.InvocationTargetException import java.lang.reflect.Array as JArray +import org.objectweb.asm.tree.analysis.Interpreter +import org.objectweb.asm.tree.analysis.Frame fun suite(): TestSuite { val suite = TestSuite() @@ -72,8 +74,12 @@ fun doTest(ownerClass: Class, methodNode: MethodNode): TestCase? { override fun runTest() { val value = interpreterLoop( - ownerClass.getInternalName(), methodNode, + initFrame( + ownerClass.getInternalName(), + methodNode, + SingleInstructionInterpreter(REFLECTION_EVAL) + ), REFLECTION_EVAL ) @@ -82,6 +88,35 @@ fun doTest(ownerClass: Class, methodNode: MethodNode): TestCase? { } } +fun initFrame( + owner: String, + m: MethodNode, + interpreter: Interpreter +): Frame { + val current = Frame(m.maxLocals, m.maxStack) + current.setReturn(interpreter.newValue(Type.getReturnType(m.desc))) + + var local = 0 + if ((m.access and ACC_STATIC) == 0) { + val ctype = Type.getObjectType(owner) + current.setLocal(local++, interpreter.newValue(ctype)) + } + + val args = Type.getArgumentTypes(m.desc) + for (i in 0..args.size - 1) { + current.setLocal(local++, interpreter.newValue(args[i])) + if (args[i].getSize() == 2) { + current.setLocal(local++, interpreter.newValue(null)) + } + } + + while (local < m.maxLocals) { + current.setLocal(local++, interpreter.newValue(null)) + } + + return current +} + fun objectToValue(obj: Any?, expectedType: Type): Value { return when (expectedType.getSort()) { Type.VOID -> VOID_VALUE From 94daf4c6bc420078301f4a59f91c50697adf3d45 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sat, 12 Oct 2013 23:56:24 +0400 Subject: [PATCH 44/66] Interpreter is not needed to create uninitialized values --- src/org/jetbrains/eval4j/interpreter.kt | 5 +---- src/org/jetbrains/eval4j/values.kt | 7 +++++++ test/org/jetbrains/eval4j/test/main.kt | 22 ++++++++++------------ 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/org/jetbrains/eval4j/interpreter.kt b/src/org/jetbrains/eval4j/interpreter.kt index fb8a32e39e0..ffb94efa01c 100644 --- a/src/org/jetbrains/eval4j/interpreter.kt +++ b/src/org/jetbrains/eval4j/interpreter.kt @@ -43,10 +43,7 @@ class SingleInstructionInterpreter(private val eval: Eval) : Interpreter( return NOT_A_VALUE } - return when (`type`.getSort()) { - Type.VOID -> null - else -> NotInitialized(`type`) - } + return makeNotInitializedValue(`type`) } override fun newOperation(insn: AbstractInsnNode): Value? { diff --git a/src/org/jetbrains/eval4j/values.kt b/src/org/jetbrains/eval4j/values.kt index 0974356d0d3..b8b4c095349 100644 --- a/src/org/jetbrains/eval4j/values.kt +++ b/src/org/jetbrains/eval4j/values.kt @@ -25,6 +25,13 @@ object VOID_VALUE: Value { override fun toString() = "VOID_VALUE" } +fun makeNotInitializedValue(t: Type): Value? { + return when (t.getSort()) { + Type.VOID -> null + else -> NotInitialized(t) + } +} + class NotInitialized(override val asmType: Type): Value { override val valid = false override fun toString() = "NotInitialized: $asmType" diff --git a/test/org/jetbrains/eval4j/test/main.kt b/test/org/jetbrains/eval4j/test/main.kt index 0ee8ee76ccc..d8b0b0e5a04 100644 --- a/test/org/jetbrains/eval4j/test/main.kt +++ b/test/org/jetbrains/eval4j/test/main.kt @@ -77,8 +77,7 @@ fun doTest(ownerClass: Class, methodNode: MethodNode): TestCase? { methodNode, initFrame( ownerClass.getInternalName(), - methodNode, - SingleInstructionInterpreter(REFLECTION_EVAL) + methodNode ), REFLECTION_EVAL ) @@ -88,30 +87,29 @@ fun doTest(ownerClass: Class, methodNode: MethodNode): TestCase? { } } -fun initFrame( +fun initFrame( owner: String, - m: MethodNode, - interpreter: Interpreter -): Frame { - val current = Frame(m.maxLocals, m.maxStack) - current.setReturn(interpreter.newValue(Type.getReturnType(m.desc))) + m: MethodNode +): Frame { + val current = Frame(m.maxLocals, m.maxStack) + current.setReturn(makeNotInitializedValue(Type.getReturnType(m.desc))) var local = 0 if ((m.access and ACC_STATIC) == 0) { val ctype = Type.getObjectType(owner) - current.setLocal(local++, interpreter.newValue(ctype)) + current.setLocal(local++, makeNotInitializedValue(ctype)) } val args = Type.getArgumentTypes(m.desc) for (i in 0..args.size - 1) { - current.setLocal(local++, interpreter.newValue(args[i])) + current.setLocal(local++, makeNotInitializedValue(args[i])) if (args[i].getSize() == 2) { - current.setLocal(local++, interpreter.newValue(null)) + current.setLocal(local++, NOT_A_VALUE) } } while (local < m.maxLocals) { - current.setLocal(local++, interpreter.newValue(null)) + current.setLocal(local++, NOT_A_VALUE) } return current From d139856e4ca48fd62b9a72f9a7a818518bc82fc7 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 13 Oct 2013 00:44:02 +0400 Subject: [PATCH 45/66] NewObjectValue simply carries a type, the actual object will be created later --- src/org/jetbrains/eval4j/values.kt | 2 +- test/org/jetbrains/eval4j/test/main.kt | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/org/jetbrains/eval4j/values.kt b/src/org/jetbrains/eval4j/values.kt index b8b4c095349..40acc520c37 100644 --- a/src/org/jetbrains/eval4j/values.kt +++ b/src/org/jetbrains/eval4j/values.kt @@ -66,7 +66,7 @@ class LongValue(value: Long): AbstractValue(value, Type.LONG_TYPE) class FloatValue(value: Float): AbstractValue(value, Type.FLOAT_TYPE) class DoubleValue(value: Double): AbstractValue(value, Type.DOUBLE_TYPE) class ObjectValue(value: Any?, asmType: Type): AbstractValue(value, asmType) -class NewObjectValue(val _class: Class<*>, asmType: Type): AbstractValueBase(asmType) { +class NewObjectValue(asmType: Type): AbstractValueBase(asmType) { override var value: Any? = null } diff --git a/test/org/jetbrains/eval4j/test/main.kt b/test/org/jetbrains/eval4j/test/main.kt index d8b0b0e5a04..38d343a6fa0 100644 --- a/test/org/jetbrains/eval4j/test/main.kt +++ b/test/org/jetbrains/eval4j/test/main.kt @@ -142,8 +142,7 @@ object REFLECTION_EVAL : Eval { override fun loadString(str: String): Value = ObjectValue(str, Type.getType(javaClass())) override fun newInstance(classType: Type): Value { - val _class = findClass(classType) - return NewObjectValue(_class, classType) + return NewObjectValue(classType) } override fun isInstanceOf(value: Value, targetType: Type): Boolean { @@ -298,7 +297,7 @@ object REFLECTION_EVAL : Eval { if (methodDesc.name == "") { // Constructor call [suppress("UNCHECKED_CAST")] - val _class = (instance as NewObjectValue)._class as Class + val _class = findClass((instance as NewObjectValue).asmType) val ctor = _class.findConstructor(methodDesc) assertNotNull("Constructor not found: $methodDesc", ctor) val args = arguments.map { v -> v.obj }.copyToArray() From 613890248130f476efb4630d5c9932981c66b8d6 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 13 Oct 2013 00:48:52 +0400 Subject: [PATCH 46/66] Useful method moved to values.kt --- src/org/jetbrains/eval4j/values.kt | 9 ++++++++- test/org/jetbrains/eval4j/test/main.kt | 7 ------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/org/jetbrains/eval4j/values.kt b/src/org/jetbrains/eval4j/values.kt index 40acc520c37..db6617d286c 100644 --- a/src/org/jetbrains/eval4j/values.kt +++ b/src/org/jetbrains/eval4j/values.kt @@ -97,4 +97,11 @@ val Value.obj: Any? return v } return (this as AbstractValue<*>).value - } \ No newline at end of file + } + +fun Any?.checkNull(): Any { + if (this == null) { + throw ThrownFromEvalException(ObjectValue(NullPointerException(), Type.getType(javaClass()))) + } + return this +} \ No newline at end of file diff --git a/test/org/jetbrains/eval4j/test/main.kt b/test/org/jetbrains/eval4j/test/main.kt index 38d343a6fa0..65b4f0b3b62 100644 --- a/test/org/jetbrains/eval4j/test/main.kt +++ b/test/org/jetbrains/eval4j/test/main.kt @@ -225,13 +225,6 @@ object REFLECTION_EVAL : Eval { } } - fun Any?.checkNull(): Any { - if (this == null) { - throw ThrownFromEvalException(ObjectValue(NullPointerException(), Type.getType(javaClass()))) - } - return this - } - override fun getStaticField(fieldDesc: FieldDescription): Value { val field = findStaticField(fieldDesc) From 5ea384a165b9ad04682b69757ea2c1bc6517ea96 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 13 Oct 2013 00:54:35 +0400 Subject: [PATCH 47/66] ownerType supported on members --- src/org/jetbrains/eval4j/members.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/org/jetbrains/eval4j/members.kt b/src/org/jetbrains/eval4j/members.kt index 4961ca0bee4..68bdbbccc4d 100644 --- a/src/org/jetbrains/eval4j/members.kt +++ b/src/org/jetbrains/eval4j/members.kt @@ -12,6 +12,9 @@ open data class MemberDescription( val isStatic: Boolean ) +val MemberDescription.ownerType: Type + get() = Type.getObjectType(ownerInternalName) + class MethodDescription( ownerInternalName: String, name: String, From 0923be42ffbb152ec78563aea5eb83f11192e862 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 13 Oct 2013 00:59:45 +0400 Subject: [PATCH 48/66] throwException() utility introduced --- src/org/jetbrains/eval4j/values.kt | 6 +++++- test/org/jetbrains/eval4j/test/main.kt | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/org/jetbrains/eval4j/values.kt b/src/org/jetbrains/eval4j/values.kt index db6617d286c..6eb79c28d9c 100644 --- a/src/org/jetbrains/eval4j/values.kt +++ b/src/org/jetbrains/eval4j/values.kt @@ -101,7 +101,11 @@ val Value.obj: Any? fun Any?.checkNull(): Any { if (this == null) { - throw ThrownFromEvalException(ObjectValue(NullPointerException(), Type.getType(javaClass()))) + throwException(NullPointerException()) } return this +} + +fun throwException(e: Throwable): Nothing { + throw ThrownFromEvalException(ObjectValue(e, Type.getType(e.javaClass))) } \ No newline at end of file diff --git a/test/org/jetbrains/eval4j/test/main.kt b/test/org/jetbrains/eval4j/test/main.kt index 65b4f0b3b62..491abb64a16 100644 --- a/test/org/jetbrains/eval4j/test/main.kt +++ b/test/org/jetbrains/eval4j/test/main.kt @@ -221,7 +221,7 @@ object REFLECTION_EVAL : Eval { } } catch (e: Throwable) { - throw ThrownFromEvalException(ObjectValue(e, Type.getType(e.javaClass))) + throwException(e) } } From 4591363c9ecdb891c0773afb6d553062d8690b6a Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 13 Oct 2013 01:11:58 +0400 Subject: [PATCH 49/66] JDI-based values --- src/org/jetbrains/eval4j/jdi/jdiValues.kt | 58 +++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/org/jetbrains/eval4j/jdi/jdiValues.kt diff --git a/src/org/jetbrains/eval4j/jdi/jdiValues.kt b/src/org/jetbrains/eval4j/jdi/jdiValues.kt new file mode 100644 index 00000000000..f622516be18 --- /dev/null +++ b/src/org/jetbrains/eval4j/jdi/jdiValues.kt @@ -0,0 +1,58 @@ +package org.jetbrains.eval4j.jdi + +import org.jetbrains.eval4j.* +import org.objectweb.asm.tree.analysis.Frame +import org.objectweb.asm.tree.MethodNode +import org.objectweb.asm.Type +import org.objectweb.asm.Opcodes.* +import com.sun.jdi + +fun makeInitialFrame(methodNode: MethodNode, arguments: List): Frame { + val isStatic = (methodNode.access and ACC_STATIC) != 0 + assert(isStatic, "Instance methods are not supported: $methodNode") + + val params = Type.getArgumentTypes(methodNode.desc) + assert(params.size == arguments.size(), "Wrong number of arguments for $methodNode: $arguments") + + val frame = Frame(methodNode.maxLocals, methodNode.maxStack) + frame.setReturn(makeNotInitializedValue(Type.getReturnType(methodNode.desc))) + + for ((i, arg) in arguments.withIndices()) { + frame.setLocal(i, arg) + } + + for (i in arguments.size..methodNode.maxLocals - 1) { + frame.setLocal(i, NOT_A_VALUE) + } + + return frame +} + +class JDIFailureException(message: String?, cause: Throwable? = null): RuntimeException(message, cause) + +fun T?.sure(message: String? = null): T = this ?: throw JDIFailureException(message) + +fun jdi.Value?.asValue(): Value { + return when (this) { + null -> NULL_VALUE + is jdi.VoidValue -> VOID_VALUE + is jdi.BooleanValue -> IntValue(intValue(), Type.BOOLEAN_TYPE) + is jdi.ByteValue -> IntValue(intValue(), Type.BYTE_TYPE) + is jdi.ShortValue -> IntValue(intValue(), Type.SHORT_TYPE) + is jdi.CharValue -> IntValue(intValue(), Type.CHAR_TYPE) + is jdi.IntegerValue -> IntValue(intValue(), Type.INT_TYPE) + is jdi.LongValue -> LongValue(longValue()) + is jdi.FloatValue -> FloatValue(floatValue()) + is jdi.DoubleValue -> DoubleValue(doubleValue()) + is jdi.ObjectReference -> ObjectValue(this, `type`().asType()) + else -> throw JDIFailureException("Unknown value: $this") + } +} + +fun jdi.Type.asType(): Type = Type.getType(this.signature()) + +val Value.jdiObj: jdi.ObjectReference? + get() = this.obj as jdi.ObjectReference? + +val Value.jdiClass: jdi.ClassObjectReference? + get() = this.jdiObj as jdi.ClassObjectReference? \ No newline at end of file From f17fa36791012155ae3f82fb28bc8f38e5b839eb Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 13 Oct 2013 01:12:27 +0400 Subject: [PATCH 50/66] JDI-based Eval for classes and static fields --- annotations/jdi/com/sun/jdi/annotations.xml | 20 ++++ src/org/jetbrains/eval4j/jdi/jdiEval.kt | 113 ++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 annotations/jdi/com/sun/jdi/annotations.xml create mode 100644 src/org/jetbrains/eval4j/jdi/jdiEval.kt diff --git a/annotations/jdi/com/sun/jdi/annotations.xml b/annotations/jdi/com/sun/jdi/annotations.xml new file mode 100644 index 00000000000..baac6359e80 --- /dev/null +++ b/annotations/jdi/com/sun/jdi/annotations.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/org/jetbrains/eval4j/jdi/jdiEval.kt b/src/org/jetbrains/eval4j/jdi/jdiEval.kt new file mode 100644 index 00000000000..73a39dcb563 --- /dev/null +++ b/src/org/jetbrains/eval4j/jdi/jdiEval.kt @@ -0,0 +1,113 @@ +package org.jetbrains.eval4j.jdi + +import org.jetbrains.eval4j.* +import org.objectweb.asm.Type +import com.sun.jdi + +val CLASS = Type.getType(javaClass>()) + +class JDIEval( + private val vm: jdi.VirtualMachine, + private val classLoader: jdi.ClassLoaderReference, + private val thread: jdi.ThreadReference +) : Eval { + override fun loadClass(classType: Type): Value { + return invokeStaticMethod( + MethodDescription( + CLASS.getInternalName(), + "forName", + "(Ljava/lang/String;Ljava/lang/ClassLoader;)Ljava/lang/Class;", + true + ), + listOf(classLoader.asValue()) + ) + } + + override fun loadString(str: String): Value = vm.mirrorOf(str)!!.asValue() + + override fun newInstance(classType: Type): Value { + return NewObjectValue(classType) + } + + override fun isInstanceOf(value: Value, targetType: Type): Boolean { + assert(targetType.getSort() == Type.OBJECT, "Can't check isInstanceOf() for non-object type $targetType") + + val _class = loadClass(targetType) + return invokeMethod( + _class, + MethodDescription( + CLASS.getInternalName(), + "isInstance", + "(Ljava/lang/Object;)Z", + false + ), + listOf(value)).boolean + } + + override fun newArray(arrayType: Type, size: Int): Value { + throw UnsupportedOperationException() + } + override fun newMultiDimensionalArray(arrayType: Type, dimensionSizes: List): Value { + throw UnsupportedOperationException() + } + override fun getArrayLength(array: Value): Value { + throw UnsupportedOperationException() + } + override fun getArrayElement(array: Value, index: Value): Value { + throw UnsupportedOperationException() + } + override fun setArrayElement(array: Value, index: Value, newValue: Value) { + throw UnsupportedOperationException() + } + + private fun findField(fieldDesc: FieldDescription): jdi.Field { + val _class = loadClass(fieldDesc.ownerType).jdiClass!!.reflectedType() + val field = _class.fieldByName(fieldDesc.name) + if (field == null) { + throwException(NoSuchFieldError("Field not found: $fieldDesc")) + } + return field + } + + private fun findStaticField(fieldDesc: FieldDescription): jdi.Field { + val field = findField(fieldDesc) + if (!field.isStatic()) { + throwException(NoSuchFieldError("Field is not static: $fieldDesc")) + } + return field + } + + override fun getStaticField(fieldDesc: FieldDescription): Value { + val field = findStaticField(fieldDesc) + return field.declaringType().getValue(field).asValue() + } + + override fun setStaticField(fieldDesc: FieldDescription, newValue: Value) { + val field = findStaticField(fieldDesc) + + if (field.isFinal()) { + throwException(NoSuchFieldError("Can't modify a final field: $field")) + } + + val _class = field.declaringType() + if (_class !is jdi.ClassType) { + throwException(NoSuchFieldError("Can't a field in a non-class: $field")) + } + + _class.setValue(field, newValue.asJdiValue(vm)) + } + + override fun invokeStaticMethod(methodDesc: MethodDescription, arguments: List): Value { + throw UnsupportedOperationException() + } + + override fun getField(instance: Value, fieldDesc: FieldDescription): Value { + throw UnsupportedOperationException() + } + override fun setField(instance: Value, fieldDesc: FieldDescription, newValue: Value) { + throw UnsupportedOperationException() + } + override fun invokeMethod(instance: Value, methodDesc: MethodDescription, arguments: List, invokespecial: Boolean): Value { + throw UnsupportedOperationException() + } +} \ No newline at end of file From 3e26f205b39e2b65291764084fb3835674da1b8c Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 13 Oct 2013 11:53:07 +0400 Subject: [PATCH 51/66] Static method calls supported --- annotations/jdi/com/sun/jdi/annotations.xml | 6 ++++++ src/org/jetbrains/eval4j/jdi/jdiEval.kt | 24 ++++++++++++++++++++- src/org/jetbrains/eval4j/jdi/jdiValues.kt | 23 +++++++++++++++++++- 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/annotations/jdi/com/sun/jdi/annotations.xml b/annotations/jdi/com/sun/jdi/annotations.xml index baac6359e80..67939f35ef4 100644 --- a/annotations/jdi/com/sun/jdi/annotations.xml +++ b/annotations/jdi/com/sun/jdi/annotations.xml @@ -5,12 +5,18 @@ + + + + + + diff --git a/src/org/jetbrains/eval4j/jdi/jdiEval.kt b/src/org/jetbrains/eval4j/jdi/jdiEval.kt index 73a39dcb563..727bf7f5c30 100644 --- a/src/org/jetbrains/eval4j/jdi/jdiEval.kt +++ b/src/org/jetbrains/eval4j/jdi/jdiEval.kt @@ -97,8 +97,30 @@ class JDIEval( _class.setValue(field, newValue.asJdiValue(vm)) } + private fun findMethod(methodDesc: MethodDescription): jdi.Method { + val _class = loadClass(methodDesc.ownerType).jdiClass!!.reflectedType() + val method = when (_class) { + is jdi.ClassType -> { + val m = _class.concreteMethodByName(methodDesc.name, methodDesc.desc) + if (m == null) listOf() else listOf(m) + } + else -> _class.methodsByName(methodDesc.name, methodDesc.desc) + } + if (method.isEmpty()) { + throwException(NoSuchMethodError("Method not found: $methodDesc")) + } + return method[0] + } + override fun invokeStaticMethod(methodDesc: MethodDescription, arguments: List): Value { - throw UnsupportedOperationException() + val method = findMethod(methodDesc) + if (!method.isStatic()) { + throwException(NoSuchMethodError("Method is not static: $methodDesc")) + } + val _class = method.declaringType() + if (_class !is jdi.ClassType) throwException(NoSuchMethodError("Static method is a non-class type: $method")) + + return _class.invokeMethod(thread, method, arguments.map {v -> v.asJdiValue(vm)}, 0).asValue() } override fun getField(instance: Value, fieldDesc: FieldDescription): Value { diff --git a/src/org/jetbrains/eval4j/jdi/jdiValues.kt b/src/org/jetbrains/eval4j/jdi/jdiValues.kt index f622516be18..73d7fe55a38 100644 --- a/src/org/jetbrains/eval4j/jdi/jdiValues.kt +++ b/src/org/jetbrains/eval4j/jdi/jdiValues.kt @@ -55,4 +55,25 @@ val Value.jdiObj: jdi.ObjectReference? get() = this.obj as jdi.ObjectReference? val Value.jdiClass: jdi.ClassObjectReference? - get() = this.jdiObj as jdi.ClassObjectReference? \ No newline at end of file + get() = this.jdiObj as jdi.ClassObjectReference? + +fun Value.asJdiValue(vm: jdi.VirtualMachine): jdi.Value? { + return when (this) { + NULL_VALUE -> null + VOID_VALUE -> vm.mirrorOfVoid() + is IntValue -> when (asmType) { + Type.BOOLEAN_TYPE -> vm.mirrorOf(boolean) + Type.BYTE_TYPE -> vm.mirrorOf(int.toByte()) + Type.SHORT_TYPE -> vm.mirrorOf(int.toShort()) + Type.CHAR_TYPE -> vm.mirrorOf(int.toChar()) + Type.INT_TYPE -> vm.mirrorOf(int) + else -> throw JDIFailureException("Unknown value type: $this") + } + is LongValue -> vm.mirrorOf(value) + is FloatValue -> vm.mirrorOf(value) + is DoubleValue -> vm.mirrorOf(value) + is ObjectValue -> value as jdi.ObjectReference + is NewObjectValue -> throw JDIFailureException("Illegal value: $this") + else -> throw JDIFailureException("Unknown value: $this") + } +} \ No newline at end of file From a5dba2938d5f599342912d98f1a2aff5b17511f1 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 13 Oct 2013 12:01:00 +0400 Subject: [PATCH 52/66] Support instance fields and methods --- src/org/jetbrains/eval4j/jdi/jdiEval.kt | 17 ++++++++++++++--- src/org/jetbrains/eval4j/values.kt | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/org/jetbrains/eval4j/jdi/jdiEval.kt b/src/org/jetbrains/eval4j/jdi/jdiEval.kt index 727bf7f5c30..32888995f0a 100644 --- a/src/org/jetbrains/eval4j/jdi/jdiEval.kt +++ b/src/org/jetbrains/eval4j/jdi/jdiEval.kt @@ -124,12 +124,23 @@ class JDIEval( } override fun getField(instance: Value, fieldDesc: FieldDescription): Value { - throw UnsupportedOperationException() + val field = findField(fieldDesc) + val obj = instance.jdiObj.checkNull() + + return obj.getValue(field).asValue() } + override fun setField(instance: Value, fieldDesc: FieldDescription, newValue: Value) { - throw UnsupportedOperationException() + val field = findField(fieldDesc) + val obj = instance.jdiObj.checkNull() + + return obj.setValue(field, newValue.asJdiValue(vm)) } + override fun invokeMethod(instance: Value, methodDesc: MethodDescription, arguments: List, invokespecial: Boolean): Value { - throw UnsupportedOperationException() + val method = findMethod(methodDesc) + + val obj = instance.jdiObj.checkNull() + return obj.invokeMethod(thread, method, arguments.map { v -> v.asJdiValue(vm) }, 0).asValue() } } \ No newline at end of file diff --git a/src/org/jetbrains/eval4j/values.kt b/src/org/jetbrains/eval4j/values.kt index 6eb79c28d9c..ec28d1082c4 100644 --- a/src/org/jetbrains/eval4j/values.kt +++ b/src/org/jetbrains/eval4j/values.kt @@ -99,7 +99,7 @@ val Value.obj: Any? return (this as AbstractValue<*>).value } -fun Any?.checkNull(): Any { +fun T?.checkNull(): T { if (this == null) { throwException(NullPointerException()) } From 205fe325d95985cf4bdae878df7f67a4b65bb8d8 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 13 Oct 2013 12:33:42 +0400 Subject: [PATCH 53/66] Support constructor calls --- src/org/jetbrains/eval4j/jdi/jdiEval.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/org/jetbrains/eval4j/jdi/jdiEval.kt b/src/org/jetbrains/eval4j/jdi/jdiEval.kt index 32888995f0a..5743a462d0d 100644 --- a/src/org/jetbrains/eval4j/jdi/jdiEval.kt +++ b/src/org/jetbrains/eval4j/jdi/jdiEval.kt @@ -138,6 +138,20 @@ class JDIEval( } override fun invokeMethod(instance: Value, methodDesc: MethodDescription, arguments: List, invokespecial: Boolean): Value { + if (invokespecial) { + if (methodDesc.name == "") { + // Constructor call + val ctor = findMethod(methodDesc) + val _class = (instance as NewObjectValue).asmType.asReferenceType() as jdi.ClassType + val result = _class.newInstance(thread, ctor, arguments.map { v -> v.asJdiValue(vm) }, 0) + instance.value = result + return result.asValue() + } + else { + // TODO + throw UnsupportedOperationException("invokespecial is not suported yet") + } + } val method = findMethod(methodDesc) val obj = instance.jdiObj.checkNull() From 517ccf347ce0e79a4f5f557af9f7402ee6e60e05 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 13 Oct 2013 12:29:07 +0400 Subject: [PATCH 54/66] Support arrays --- src/org/jetbrains/eval4j/jdi/jdiEval.kt | 43 +++++++++++++++++++++---- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/src/org/jetbrains/eval4j/jdi/jdiEval.kt b/src/org/jetbrains/eval4j/jdi/jdiEval.kt index 5743a462d0d..f9e09e380cc 100644 --- a/src/org/jetbrains/eval4j/jdi/jdiEval.kt +++ b/src/org/jetbrains/eval4j/jdi/jdiEval.kt @@ -44,24 +44,53 @@ class JDIEval( listOf(value)).boolean } + fun Type.asReferenceType(): jdi.ReferenceType = loadClass(this).jdiClass!!.reflectedType() + fun Type.asArrayType(): jdi.ArrayType = asReferenceType() as jdi.ArrayType + override fun newArray(arrayType: Type, size: Int): Value { - throw UnsupportedOperationException() + val jdiArrayType = arrayType.asArrayType() + return jdiArrayType.newInstance(size).asValue() } + + private val Type.arrayElementType: Type + get(): Type { + assert(getSort() == Type.ARRAY, "Not an array type: $this") + return Type.getType(getDescriptor().substring(1)) + } + + private fun fillArray(elementType: Type, size: Int, nestedSizes: List): Value { + val arr = newArray(Type.getType("[" + elementType.getDescriptor()), size) + if (!nestedSizes.isEmpty()) { + val nestedElementType = elementType.arrayElementType + val nestedSize = nestedSizes[0] + val tail = nestedSizes.tail + for (i in 0..size - 1) { + setArrayElement(arr, int(i), fillArray(nestedElementType, nestedSize, tail)) + } + } + return arr + } + override fun newMultiDimensionalArray(arrayType: Type, dimensionSizes: List): Value { - throw UnsupportedOperationException() + return fillArray(arrayType.arrayElementType, dimensionSizes[0], dimensionSizes.tail) } + + private fun Value.array() = jdiObj.checkNull() as jdi.ArrayReference + override fun getArrayLength(array: Value): Value { - throw UnsupportedOperationException() + return int(array.array().length()) } + override fun getArrayElement(array: Value, index: Value): Value { - throw UnsupportedOperationException() + return array.array().getValue(index.int).asValue() } + override fun setArrayElement(array: Value, index: Value, newValue: Value) { - throw UnsupportedOperationException() + array.array().setValue(index.int, newValue.asJdiValue(vm)) } private fun findField(fieldDesc: FieldDescription): jdi.Field { - val _class = loadClass(fieldDesc.ownerType).jdiClass!!.reflectedType() + val _class = fieldDesc.ownerType.asReferenceType() val field = _class.fieldByName(fieldDesc.name) if (field == null) { throwException(NoSuchFieldError("Field not found: $fieldDesc")) @@ -98,7 +127,7 @@ class JDIEval( } private fun findMethod(methodDesc: MethodDescription): jdi.Method { - val _class = loadClass(methodDesc.ownerType).jdiClass!!.reflectedType() + val _class = methodDesc.ownerType.asReferenceType() val method = when (_class) { is jdi.ClassType -> { val m = _class.concreteMethodByName(methodDesc.name, methodDesc.desc) From ec434411904c1ad0399b6dae1e968ca188803b03 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 13 Oct 2013 15:12:26 +0400 Subject: [PATCH 55/66] Generic mechanism for creating test suites --- test/org/jetbrains/eval4j/test/main.kt | 77 ++++--------------- .../org/jetbrains/eval4j/test/suiteBuilder.kt | 76 ++++++++++++++++++ 2 files changed, 89 insertions(+), 64 deletions(-) create mode 100644 test/org/jetbrains/eval4j/test/suiteBuilder.kt diff --git a/test/org/jetbrains/eval4j/test/main.kt b/test/org/jetbrains/eval4j/test/main.kt index 491abb64a16..fdcbaab200c 100644 --- a/test/org/jetbrains/eval4j/test/main.kt +++ b/test/org/jetbrains/eval4j/test/main.kt @@ -13,80 +13,29 @@ import java.lang.reflect.Field import java.lang.reflect.Constructor import java.lang.reflect.InvocationTargetException import java.lang.reflect.Array as JArray -import org.objectweb.asm.tree.analysis.Interpreter import org.objectweb.asm.tree.analysis.Frame -fun suite(): TestSuite { - val suite = TestSuite() +fun suite(): TestSuite = buildTestSuite { + methodNode, ownerClass, expected -> + object : TestCase("test" + methodNode.name.capitalize()) { - val ownerClass = javaClass() - val inputStream = ownerClass.getClassLoader()!!.getResourceAsStream(ownerClass.getInternalName() + ".class")!! + override fun runTest() { + val value = interpreterLoop( + methodNode, + initFrame( + ownerClass.getInternalName(), + methodNode + ), + REFLECTION_EVAL + ) - ClassReader(inputStream).accept(object : ClassVisitor(ASM4) { - - override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array?): MethodVisitor? { - return object : MethodNode(access, name, desc, signature, exceptions) { - override fun visitEnd() { - val testCase = doTest(ownerClass, this) - if (testCase != null) { - suite.addTest(testCase) - } - } + assertEquals(expected, value) } } - }, 0) - - return suite } fun Class<*>.getInternalName(): String = Type.getType(this).getInternalName() -fun doTest(ownerClass: Class, methodNode: MethodNode): TestCase? { - var expected: InterpreterResult? = null - for (method in ownerClass.getDeclaredMethods()) { - if (method.getName() == methodNode.name) { - if ((method.getModifiers() and Modifier.STATIC) == 0) { - println("Skipping instance method: $method") - } - else if (method.getParameterTypes()!!.size > 0) { - println("Skipping method with parameters: $method") - } - else { - method.setAccessible(true) - val result = method.invoke(null) - val returnType = Type.getType(method.getReturnType()!!) - try { - expected = ValueReturned(objectToValue(result, returnType)) - } - catch (e: UnsupportedOperationException) { - println("Skipping $method: $e") - } - } - } - } - - if (expected == null) { - println("Method not found: ${methodNode.name}") - return null - } - - return object : TestCase("test" + methodNode.name.capitalize()) { - - override fun runTest() { - val value = interpreterLoop( - methodNode, - initFrame( - ownerClass.getInternalName(), - methodNode - ), - REFLECTION_EVAL - ) - - assertEquals(expected, value) - } - } -} - fun initFrame( owner: String, m: MethodNode diff --git a/test/org/jetbrains/eval4j/test/suiteBuilder.kt b/test/org/jetbrains/eval4j/test/suiteBuilder.kt new file mode 100644 index 00000000000..8b26aa120b8 --- /dev/null +++ b/test/org/jetbrains/eval4j/test/suiteBuilder.kt @@ -0,0 +1,76 @@ +package org.jetbrains.eval4j.test + +import org.objectweb.asm.* +import org.objectweb.asm.Opcodes.* +import org.objectweb.asm.tree.MethodNode +import java.lang.reflect.Modifier +import org.jetbrains.eval4j.* +import org.junit.Assert.* +import junit.framework.TestSuite +import junit.framework.TestCase +import java.lang.reflect.Method +import java.lang.reflect.Field +import java.lang.reflect.Constructor +import java.lang.reflect.InvocationTargetException +import java.lang.reflect.Array as JArray +import org.objectweb.asm.tree.analysis.Interpreter +import org.objectweb.asm.tree.analysis.Frame + +fun buildTestSuite( + create: (MethodNode, Class<*>, InterpreterResult?) -> TestCase +): TestSuite { + val suite = TestSuite() + + val ownerClass = javaClass() + val inputStream = ownerClass.getClassLoader()!!.getResourceAsStream(ownerClass.getInternalName() + ".class")!! + + ClassReader(inputStream).accept(object : ClassVisitor(ASM4) { + + override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array?): MethodVisitor? { + return object : MethodNode(access, name, desc, signature, exceptions) { + override fun visitEnd() { + val testCase = buildTestCase(ownerClass, this, create) + if (testCase != null) { + suite.addTest(testCase) + } + } + } + } + }, 0) + + return suite +} + +fun buildTestCase(ownerClass: Class, + methodNode: MethodNode, + create: (MethodNode, Class, InterpreterResult?) -> TestCase): TestCase? { + var expected: InterpreterResult? = null + for (method in ownerClass.getDeclaredMethods()) { + if (method.getName() == methodNode.name) { + if ((method.getModifiers() and Modifier.STATIC) == 0) { + println("Skipping instance method: $method") + } + else if (method.getParameterTypes()!!.size > 0) { + println("Skipping method with parameters: $method") + } + else { + method.setAccessible(true) + val result = method.invoke(null) + val returnType = Type.getType(method.getReturnType()!!) + try { + expected = ValueReturned(objectToValue(result, returnType)) + } + catch (e: UnsupportedOperationException) { + println("Skipping $method: $e") + } + } + } + } + + if (expected == null) { + println("Method not found: ${methodNode.name}") + return null + } + + return create(methodNode, ownerClass, expected) +} \ No newline at end of file From 88bb11383458fe4714a13156fd86b07a88be72b7 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 13 Oct 2013 15:36:21 +0400 Subject: [PATCH 56/66] Prevent infinite recursion in loadClass() --- src/org/jetbrains/eval4j/jdi/jdiEval.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/org/jetbrains/eval4j/jdi/jdiEval.kt b/src/org/jetbrains/eval4j/jdi/jdiEval.kt index f9e09e380cc..e7dc6b4fce0 100644 --- a/src/org/jetbrains/eval4j/jdi/jdiEval.kt +++ b/src/org/jetbrains/eval4j/jdi/jdiEval.kt @@ -12,14 +12,22 @@ class JDIEval( private val thread: jdi.ThreadReference ) : Eval { override fun loadClass(classType: Type): Value { + val loadedClasses = vm.classesByName(classType.getInternalName()) + if (!loadedClasses.isEmpty()) { + return loadedClasses[0].classObject().asValue() + } return invokeStaticMethod( MethodDescription( CLASS.getInternalName(), "forName", - "(Ljava/lang/String;Ljava/lang/ClassLoader;)Ljava/lang/Class;", + "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;", true ), - listOf(classLoader.asValue()) + listOf( + vm.mirrorOf(classType.getInternalName().replace('/', '.')).asValue(), + boolean(true), + classLoader.asValue() + ) ) } From 848a9c56b2c6883a999f4b9d69e46ed1b14834d7 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 13 Oct 2013 15:39:12 +0400 Subject: [PATCH 57/66] Better remote exception support --- src/org/jetbrains/eval4j/jdi/jdiEval.kt | 30 +++++++++++++++++++------ 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/org/jetbrains/eval4j/jdi/jdiEval.kt b/src/org/jetbrains/eval4j/jdi/jdiEval.kt index e7dc6b4fce0..a60f10587a9 100644 --- a/src/org/jetbrains/eval4j/jdi/jdiEval.kt +++ b/src/org/jetbrains/eval4j/jdi/jdiEval.kt @@ -116,7 +116,7 @@ class JDIEval( override fun getStaticField(fieldDesc: FieldDescription): Value { val field = findStaticField(fieldDesc) - return field.declaringType().getValue(field).asValue() + return mayThrow { field.declaringType().getValue(field) }.asValue() } override fun setStaticField(fieldDesc: FieldDescription, newValue: Value) { @@ -131,7 +131,8 @@ class JDIEval( throwException(NoSuchFieldError("Can't a field in a non-class: $field")) } - _class.setValue(field, newValue.asJdiValue(vm)) + val jdiValue = newValue.asJdiValue(vm) + mayThrow { _class.setValue(field, jdiValue) } } private fun findMethod(methodDesc: MethodDescription): jdi.Method { @@ -157,21 +158,24 @@ class JDIEval( val _class = method.declaringType() if (_class !is jdi.ClassType) throwException(NoSuchMethodError("Static method is a non-class type: $method")) - return _class.invokeMethod(thread, method, arguments.map {v -> v.asJdiValue(vm)}, 0).asValue() + val args = arguments.map { v -> v.asJdiValue(vm) } + val result = mayThrow { _class.invokeMethod(thread, method, args, 0) } + return result.asValue() } override fun getField(instance: Value, fieldDesc: FieldDescription): Value { val field = findField(fieldDesc) val obj = instance.jdiObj.checkNull() - return obj.getValue(field).asValue() + return mayThrow { obj.getValue(field) }.asValue() } override fun setField(instance: Value, fieldDesc: FieldDescription, newValue: Value) { val field = findField(fieldDesc) val obj = instance.jdiObj.checkNull() - return obj.setValue(field, newValue.asJdiValue(vm)) + val jdiValue = newValue.asJdiValue(vm) + mayThrow { obj.setValue(field, jdiValue) } } override fun invokeMethod(instance: Value, methodDesc: MethodDescription, arguments: List, invokespecial: Boolean): Value { @@ -180,7 +184,8 @@ class JDIEval( // Constructor call val ctor = findMethod(methodDesc) val _class = (instance as NewObjectValue).asmType.asReferenceType() as jdi.ClassType - val result = _class.newInstance(thread, ctor, arguments.map { v -> v.asJdiValue(vm) }, 0) + val args = arguments.map { v -> v.asJdiValue(vm) } + val result = mayThrow { _class.newInstance(thread, ctor, args, 0) } instance.value = result return result.asValue() } @@ -192,6 +197,17 @@ class JDIEval( val method = findMethod(methodDesc) val obj = instance.jdiObj.checkNull() - return obj.invokeMethod(thread, method, arguments.map { v -> v.asJdiValue(vm) }, 0).asValue() + val args = arguments.map { v -> v.asJdiValue(vm) } + val result = mayThrow { obj.invokeMethod(thread, method, args, 0) } + return result.asValue() + } +} + +fun mayThrow(f: () -> T): T { + try { + return f() + } + catch (e: jdi.InvocationException) { + throw ThrownFromEvalException(e.exception().asValue()) } } \ No newline at end of file From 1c07c42b2ca0a4bb71bd2dda8578c4b06f26a5f9 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 13 Oct 2013 16:01:55 +0400 Subject: [PATCH 58/66] better toString() --- src/org/jetbrains/eval4j/interpreterLoop.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/org/jetbrains/eval4j/interpreterLoop.kt b/src/org/jetbrains/eval4j/interpreterLoop.kt index 6b11505da2a..d9dcfc1eaa1 100644 --- a/src/org/jetbrains/eval4j/interpreterLoop.kt +++ b/src/org/jetbrains/eval4j/interpreterLoop.kt @@ -45,7 +45,9 @@ trait InterpretationEventHandler { fun exceptionCaught(currentState: Frame, currentInsn: AbstractInsnNode, exception: Value): InterpreterResult? } -class ThrownFromEvalException(val exception: Value): RuntimeException() +class ThrownFromEvalException(val exception: Value): RuntimeException() { + fun toString(): String = "Thrown by evaluator: $exception" +} fun interpreterLoop( m: MethodNode, From 9814726f9d5e687f1b26a5d94ec8a2b2677c3bf9 Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Sun, 13 Oct 2013 16:02:20 +0400 Subject: [PATCH 59/66] JDI-based tests run, many fail --- .../{Test.xml => Reflection_Test.xml} | 2 +- annotations/jdi/com/sun/jdi/annotations.xml | 30 +++++ .../jdi/com/sun/jdi/connect/annotations.xml | 5 + .../jdi/com/sun/jdi/event/annotations.xml | 8 ++ .../jdi/com/sun/jdi/request/annotations.xml | 8 ++ annotations/jdi/java/lang/annotations.xml | 8 ++ .../jetbrains/eval4j/jdi/test/Debugee.java | 9 ++ test/org/jetbrains/eval4j/jdi/test/jdiTest.kt | 117 ++++++++++++++++++ 8 files changed, 186 insertions(+), 1 deletion(-) rename .idea/runConfigurations/{Test.xml => Reflection_Test.xml} (93%) create mode 100644 annotations/jdi/com/sun/jdi/connect/annotations.xml create mode 100644 annotations/jdi/com/sun/jdi/event/annotations.xml create mode 100644 annotations/jdi/com/sun/jdi/request/annotations.xml create mode 100644 annotations/jdi/java/lang/annotations.xml create mode 100644 test/org/jetbrains/eval4j/jdi/test/Debugee.java create mode 100644 test/org/jetbrains/eval4j/jdi/test/jdiTest.kt diff --git a/.idea/runConfigurations/Test.xml b/.idea/runConfigurations/Reflection_Test.xml similarity index 93% rename from .idea/runConfigurations/Test.xml rename to .idea/runConfigurations/Reflection_Test.xml index 62cf139b6e9..71cd9c632f9 100644 --- a/.idea/runConfigurations/Test.xml +++ b/.idea/runConfigurations/Reflection_Test.xml @@ -1,5 +1,5 @@ - +