diff --git a/eval4j/.gitignore b/eval4j/.gitignore new file mode 100644 index 00000000000..673af156045 --- /dev/null +++ b/eval4j/.gitignore @@ -0,0 +1,3 @@ +out +.idea/workspace.xml +.idea/uiDesigner.xml diff --git a/eval4j/.idea/.name b/eval4j/.idea/.name new file mode 100644 index 00000000000..731b3d6f54e --- /dev/null +++ b/eval4j/.idea/.name @@ -0,0 +1 @@ +eval4j \ No newline at end of file diff --git a/eval4j/.idea/compiler.xml b/eval4j/.idea/compiler.xml new file mode 100644 index 00000000000..217af471a9e --- /dev/null +++ b/eval4j/.idea/compiler.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/eval4j/.idea/copyright/profiles_settings.xml b/eval4j/.idea/copyright/profiles_settings.xml new file mode 100644 index 00000000000..3572571ad83 --- /dev/null +++ b/eval4j/.idea/copyright/profiles_settings.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/eval4j/.idea/encodings.xml b/eval4j/.idea/encodings.xml new file mode 100644 index 00000000000..e206d70d859 --- /dev/null +++ b/eval4j/.idea/encodings.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/eval4j/.idea/kannotator.xml b/eval4j/.idea/kannotator.xml new file mode 100644 index 00000000000..48c712c3946 --- /dev/null +++ b/eval4j/.idea/kannotator.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/eval4j/.idea/libraries/KotlinJavaRuntime.xml b/eval4j/.idea/libraries/KotlinJavaRuntime.xml new file mode 100644 index 00000000000..0b98ab4814d --- /dev/null +++ b/eval4j/.idea/libraries/KotlinJavaRuntime.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/eval4j/.idea/libraries/junit_junit_4_11.xml b/eval4j/.idea/libraries/junit_junit_4_11.xml new file mode 100644 index 00000000000..2895188a94b --- /dev/null +++ b/eval4j/.idea/libraries/junit_junit_4_11.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/eval4j/.idea/libraries/org_ow2_asm_asm_debug_all_4_1.xml b/eval4j/.idea/libraries/org_ow2_asm_asm_debug_all_4_1.xml new file mode 100644 index 00000000000..7d9e29eb73c --- /dev/null +++ b/eval4j/.idea/libraries/org_ow2_asm_asm_debug_all_4_1.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eval4j/.idea/misc.xml b/eval4j/.idea/misc.xml new file mode 100644 index 00000000000..97320410eec --- /dev/null +++ b/eval4j/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/eval4j/.idea/modules.xml b/eval4j/.idea/modules.xml new file mode 100644 index 00000000000..313c2996166 --- /dev/null +++ b/eval4j/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/eval4j/.idea/runConfigurations/Reflection_Test.xml b/eval4j/.idea/runConfigurations/Reflection_Test.xml new file mode 100644 index 00000000000..71cd9c632f9 --- /dev/null +++ b/eval4j/.idea/runConfigurations/Reflection_Test.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eval4j/.idea/scopes/scope_settings.xml b/eval4j/.idea/scopes/scope_settings.xml new file mode 100644 index 00000000000..922003b8433 --- /dev/null +++ b/eval4j/.idea/scopes/scope_settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/eval4j/.idea/vcs.xml b/eval4j/.idea/vcs.xml new file mode 100644 index 00000000000..9d32e507a98 --- /dev/null +++ b/eval4j/.idea/vcs.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/eval4j/ReadMe.md b/eval4j/ReadMe.md new file mode 100644 index 00000000000..9fbab56cb9b --- /dev/null +++ b/eval4j/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 diff --git a/eval4j/annotations/jdi/com/sun/jdi/annotations.xml b/eval4j/annotations/jdi/com/sun/jdi/annotations.xml new file mode 100644 index 00000000000..5a32c14d723 --- /dev/null +++ b/eval4j/annotations/jdi/com/sun/jdi/annotations.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eval4j/annotations/jdi/com/sun/jdi/connect/annotations.xml b/eval4j/annotations/jdi/com/sun/jdi/connect/annotations.xml new file mode 100644 index 00000000000..7aaa5401216 --- /dev/null +++ b/eval4j/annotations/jdi/com/sun/jdi/connect/annotations.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/eval4j/annotations/jdi/com/sun/jdi/event/annotations.xml b/eval4j/annotations/jdi/com/sun/jdi/event/annotations.xml new file mode 100644 index 00000000000..a92ff00c346 --- /dev/null +++ b/eval4j/annotations/jdi/com/sun/jdi/event/annotations.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/eval4j/annotations/jdi/com/sun/jdi/request/annotations.xml b/eval4j/annotations/jdi/com/sun/jdi/request/annotations.xml new file mode 100644 index 00000000000..3f72d2e9760 --- /dev/null +++ b/eval4j/annotations/jdi/com/sun/jdi/request/annotations.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/eval4j/annotations/jdi/java/lang/annotations.xml b/eval4j/annotations/jdi/java/lang/annotations.xml new file mode 100644 index 00000000000..a2bfc485d6e --- /dev/null +++ b/eval4j/annotations/jdi/java/lang/annotations.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/eval4j/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/annotations.xml b/eval4j/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/annotations.xml new file mode 100644 index 00000000000..c608a4842d3 --- /dev/null +++ b/eval4j/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/annotations.xml @@ -0,0 +1,554 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eval4j/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/commons/annotations.xml b/eval4j/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/eval4j/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/commons/annotations.xml @@ -0,0 +1,1037 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eval4j/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/signature/annotations.xml b/eval4j/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/eval4j/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/signature/annotations.xml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eval4j/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/analysis/annotations.xml b/eval4j/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/analysis/annotations.xml new file mode 100644 index 00000000000..582d9bf2dc6 --- /dev/null +++ b/eval4j/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/analysis/annotations.xml @@ -0,0 +1,599 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eval4j/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml b/eval4j/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml new file mode 100644 index 00000000000..d2f989a9d19 --- /dev/null +++ b/eval4j/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/tree/annotations.xml @@ -0,0 +1,593 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eval4j/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/util/annotations.xml b/eval4j/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/eval4j/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/util/annotations.xml @@ -0,0 +1,1496 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eval4j/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/xml/annotations.xml b/eval4j/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/eval4j/annotations/org.ow2.asm_asm-debug-all_4.1/org/objectweb/asm/xml/annotations.xml @@ -0,0 +1,290 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eval4j/eval4j.iml b/eval4j/eval4j.iml new file mode 100644 index 00000000000..cc20df994ed --- /dev/null +++ b/eval4j/eval4j.iml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/eval4j/src/org/jetbrains/eval4j/interpreter.kt b/eval4j/src/org/jetbrains/eval4j/interpreter.kt new file mode 100644 index 00000000000..f70c76239e2 --- /dev/null +++ b/eval4j/src/org/jetbrains/eval4j/interpreter.kt @@ -0,0 +1,367 @@ +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.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 + +class UnsupportedByteCodeException(message: String) : RuntimeException(message) + +trait Eval { + fun loadClass(classType: Type): Value + fun loadString(str: String): Value + fun newInstance(classType: Type): Value + fun isInstanceOf(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: FieldDescription): Value + fun setStaticField(fieldDesc: FieldDescription, newValue: Value) + fun invokeStaticMethod(methodDesc: MethodDescription, arguments: List): 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 +} + +class SingleInstructionInterpreter(private val eval: Eval) : Interpreter(ASM4) { + override fun newValue(`type`: Type?): Value? { + if (`type` == null) { + return NOT_A_VALUE + } + + return makeNotInitializedValue(`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 -> eval.loadString(cst) + is Type -> { + val sort = (cst as Type).getSort() + when (sort) { + Type.OBJECT, Type.ARRAY -> eval.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) + GETSTATIC -> eval.getStaticField(FieldDescription(insn as FieldInsnNode)) + NEW -> eval.newInstance(Type.getObjectType((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 -> int(value.int + (insn as IincInsnNode).incr) + 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 -> { + // Handled by interpreter loop, see checkUnaryCondition() + null + } + + // TODO: switch + TABLESWITCH, + LOOKUPSWITCH -> throw UnsupportedByteCodeException("Switch is not supported yet") + + PUTSTATIC -> { + eval.setStaticField(FieldDescription(insn as FieldInsnNode), value) + null + } + + GETFIELD -> eval.getField(value, FieldDescription(insn as FieldInsnNode)) + + 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") + } + eval.newArray(Type.getType(typeStr), value.int) + } + ANEWARRAY -> { + val desc = (insn as TypeInsnNode).desc + eval.newArray(Type.getType("[" + Type.getObjectType(desc)), value.int) + } + ARRAYLENGTH -> eval.getArrayLength(value) + + ATHROW -> { + // Handled by interpreter loop + null + } + + CHECKCAST -> { + val targetType = Type.getObjectType((insn as TypeInsnNode).desc) + if (eval.isInstanceOf(value, targetType)) { + ObjectValue(value.obj, targetType) + } + else { + throwEvalException(ClassCastException("Value '$value' cannot be cast to $targetType")) + } + } + + INSTANCEOF -> { + val targetType = Type.getObjectType((insn as TypeInsnNode).desc) + boolean(eval.isInstanceOf(value, targetType)) + } + + // TODO: maybe just do nothing? + MONITORENTER, MONITOREXIT -> throw UnsupportedByteCodeException("Monitor instructions are not supported") + + else -> throw UnsupportedByteCodeException("$insn") + } + } + + 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, + FALOAD, LALOAD, DALOAD, + AALOAD -> eval.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 -> { + // Handled by interpreter loop, see checkBinaryCondition() + null + } + + PUTFIELD -> { + eval.setField(value1, FieldDescription(insn as FieldInsnNode), value2) + null + } + + else -> throw UnsupportedByteCodeException("$insn") + } + } + + 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 -> { + eval.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 + eval.newMultiDimensionalArray(Type.getType(node.desc), values.map { v -> v.int }) + } + + INVOKEVIRTUAL, INVOKESPECIAL, INVOKEINTERFACE -> { + eval.invokeMethod( + values[0], + MethodDescription(insn as MethodInsnNode), + values.subList(1, values.size()), + insn.getOpcode() == INVOKESPECIAL + ) + } + + INVOKESTATIC -> eval.invokeStaticMethod(MethodDescription(insn as MethodInsnNode), values) + + INVOKEDYNAMIC -> throw UnsupportedByteCodeException("INVOKEDYNAMIC is not supported") + else -> throw UnsupportedByteCodeException("$insn") + } + } + + + override fun returnOperation(insn: AbstractInsnNode, value: Value, expected: Value) { + when (insn.getOpcode()) { + IRETURN, LRETURN, FRETURN, DRETURN, ARETURN -> { + // Handled by interpreter loop + } + + else -> throw UnsupportedByteCodeException("$insn") + } + } + + override fun merge(v: Value, w: Value): Value { + // We always remember the NEW value + return w + } +} diff --git a/eval4j/src/org/jetbrains/eval4j/interpreterLoop.kt b/eval4j/src/org/jetbrains/eval4j/interpreterLoop.kt new file mode 100644 index 00000000000..b63d49a689c --- /dev/null +++ b/eval4j/src/org/jetbrains/eval4j/interpreterLoop.kt @@ -0,0 +1,248 @@ +package org.jetbrains.eval4j + +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.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 +} + +class ExceptionThrown(val exception: Value): InterpreterResult { + override fun toString(): String = "Thrown $exception" +} + +data 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 { + + 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? + + fun exceptionThrown(currentState: Frame, currentInsn: AbstractInsnNode, exception: Value): InterpreterResult? + fun exceptionCaught(currentState: Frame, currentInsn: AbstractInsnNode, exception: Value): InterpreterResult? +} + +class ThrownFromEvalException(cause: Throwable): RuntimeException(cause) { + fun toString(): String = "Thrown by evaluator: ${getCause()}" +} + +class ThrownFromEvaluatedCodeException(val exception: Value): RuntimeException() { + fun toString(): String = "Thrown from evaluated code: $exception" +} + +fun interpreterLoop( + m: MethodNode, + initialState: Frame, + eval: Eval, + handler: InterpretationEventHandler = InterpretationEventHandler.NONE +): InterpreterResult { + val firstInsn = m.instructions.getFirst() + if (firstInsn == null) throw IllegalArgumentException("Empty method") + + var currentInsn = firstInsn!! + + fun goto(nextInsn: AbstractInsnNode?) { + if (nextInsn == null) throw IllegalArgumentException("Instruction flow ended with no RETURN") + currentInsn = nextInsn + } + + val interpreter = SingleInstructionInterpreter(eval) + val frame = Frame(initialState) + val handlers = computeHandlers(m) + + class ResultException(val result: InterpreterResult): RuntimeException() + + fun exceptionCaught(exceptionValue: Value, instanceOf: (Type) -> Boolean): Boolean { + val catchBlocks = handlers[m.instructions.indexOf(currentInsn)] ?: listOf() + for (catch in catchBlocks) { + val exceptionTypeInternalName = catch.`type` + if (exceptionTypeInternalName != null) { + val exceptionType = Type.getObjectType(exceptionTypeInternalName) + if (instanceOf(exceptionType)) { + val handled = handler.exceptionCaught(frame, currentInsn, exceptionValue) + if (handled != null) throw ResultException(handled) + frame.clearStack() + frame.push(exceptionValue) + goto(catch.handler) + return true + } + } + } + return false + } + + fun exceptionCaught(exceptionValue: Value): Boolean = exceptionCaught(exceptionValue) { + exceptionType -> eval.isInstanceOf(exceptionValue, exceptionType) + } + + fun exceptionFromEvalCaught(exception: Throwable, exceptionValue: Value): Boolean { + return exceptionCaught(exceptionValue) { + exceptionType -> + try { + val exceptionClass = exception.javaClass + val _class = Class.forName( + exceptionType.getInternalName().replace('/', '.'), + true, + exceptionClass.getClassLoader() + ) + _class.isAssignableFrom(exceptionClass) + } + catch (e: ClassNotFoundException) { + // If the class is not available in this VM, it can not be a superclass of an exception trown in it + false + } + } + } + + try { + while (true) { + 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 -> { + 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) + } + 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 -> 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 + } + } + + 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 -> {} + } + + try { + frame.execute(currentInsn, interpreter) + } + catch (e: ThrownFromEvalException) { + val exception = e.getCause()!! + val exceptionValue = ObjectValue(exception, Type.getType(exception.javaClass)) + val handled = handler.exceptionThrown(frame, currentInsn, + exceptionValue) + if (handled != null) return handled + if (exceptionFromEvalCaught(exception, exceptionValue)) continue + return ExceptionThrown(exceptionValue) + } + catch (e: ThrownFromEvaluatedCodeException) { + 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()) + } + } + catch(e: ResultException) { + return e.result + } +} + +// Copied from org.objectweb.asm.tree.analysis.Analyzer.analyze() +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/eval4j/src/org/jetbrains/eval4j/jdi/jdiEval.kt b/eval4j/src/org/jetbrains/eval4j/jdi/jdiEval.kt new file mode 100644 index 00000000000..dd927dc64a4 --- /dev/null +++ b/eval4j/src/org/jetbrains/eval4j/jdi/jdiEval.kt @@ -0,0 +1,213 @@ +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 { + val loadedClasses = vm.classesByName(classType.getInternalName()) + if (!loadedClasses.isEmpty()) { + return loadedClasses[0].classObject().asValue() + } + return invokeStaticMethod( + MethodDescription( + CLASS.getInternalName(), + "forName", + "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;", + true + ), + listOf( + vm.mirrorOf(classType.getInternalName().replace('/', '.')).asValue(), + boolean(true), + 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 + } + + 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 { + 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 { + return fillArray(arrayType.arrayElementType, dimensionSizes[0], dimensionSizes.tail) + } + + private fun Value.array() = jdiObj.checkNull() as jdi.ArrayReference + + override fun getArrayLength(array: Value): Value { + return int(array.array().length()) + } + + override fun getArrayElement(array: Value, index: Value): Value { + return array.array().getValue(index.int).asValue() + } + + override fun setArrayElement(array: Value, index: Value, newValue: Value) { + array.array().setValue(index.int, newValue.asJdiValue(vm)) + } + + private fun findField(fieldDesc: FieldDescription): jdi.Field { + val _class = fieldDesc.ownerType.asReferenceType() + val field = _class.fieldByName(fieldDesc.name) + if (field == null) { + throwEvalException(NoSuchFieldError("Field not found: $fieldDesc")) + } + return field + } + + private fun findStaticField(fieldDesc: FieldDescription): jdi.Field { + val field = findField(fieldDesc) + if (!field.isStatic()) { + throwEvalException(NoSuchFieldError("Field is not static: $fieldDesc")) + } + return field + } + + override fun getStaticField(fieldDesc: FieldDescription): Value { + val field = findStaticField(fieldDesc) + return mayThrow { field.declaringType().getValue(field) }.asValue() + } + + override fun setStaticField(fieldDesc: FieldDescription, newValue: Value) { + val field = findStaticField(fieldDesc) + + if (field.isFinal()) { + throwEvalException(NoSuchFieldError("Can't modify a final field: $field")) + } + + val _class = field.declaringType() + if (_class !is jdi.ClassType) { + throwEvalException(NoSuchFieldError("Can't a field in a non-class: $field")) + } + + val jdiValue = newValue.asJdiValue(vm) + mayThrow { _class.setValue(field, jdiValue) } + } + + private fun findMethod(methodDesc: MethodDescription): jdi.Method { + val _class = methodDesc.ownerType.asReferenceType() + 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()) { + throwEvalException(NoSuchMethodError("Method not found: $methodDesc")) + } + return method[0] + } + + override fun invokeStaticMethod(methodDesc: MethodDescription, arguments: List): Value { + val method = findMethod(methodDesc) + if (!method.isStatic()) { + throwEvalException(NoSuchMethodError("Method is not static: $methodDesc")) + } + val _class = method.declaringType() + if (_class !is jdi.ClassType) throwEvalException(NoSuchMethodError("Static method is a non-class type: $method")) + + 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 mayThrow { obj.getValue(field) }.asValue() + } + + override fun setField(instance: Value, fieldDesc: FieldDescription, newValue: Value) { + val field = findField(fieldDesc) + val obj = instance.jdiObj.checkNull() + + val jdiValue = newValue.asJdiValue(vm) + mayThrow { obj.setValue(field, jdiValue) } + } + + 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 args = arguments.map { v -> v.asJdiValue(vm) } + val result = mayThrow { _class.newInstance(thread, ctor, args, 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() + 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 ThrownFromEvaluatedCodeException(e.exception().asValue()) + } +} \ No newline at end of file diff --git a/eval4j/src/org/jetbrains/eval4j/jdi/jdiValues.kt b/eval4j/src/org/jetbrains/eval4j/jdi/jdiValues.kt new file mode 100644 index 00000000000..519a645d85c --- /dev/null +++ b/eval4j/src/org/jetbrains/eval4j/jdi/jdiValues.kt @@ -0,0 +1,79 @@ +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? + +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 -> this.obj as jdi.ObjectReference + else -> throw JDIFailureException("Unknown value: $this") + } +} \ No newline at end of file diff --git a/eval4j/src/org/jetbrains/eval4j/members.kt b/eval4j/src/org/jetbrains/eval4j/members.kt new file mode 100644 index 00000000000..68bdbbccc4d --- /dev/null +++ b/eval4j/src/org/jetbrains/eval4j/members.kt @@ -0,0 +1,56 @@ +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 +) + +val MemberDescription.ownerType: Type + get() = Type.getObjectType(ownerInternalName) + +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/eval4j/src/org/jetbrains/eval4j/values.kt b/eval4j/src/org/jetbrains/eval4j/values.kt new file mode 100644 index 00000000000..445fd92fcae --- /dev/null +++ b/eval4j/src/org/jetbrains/eval4j/values.kt @@ -0,0 +1,111 @@ +package org.jetbrains.eval4j + +import org.objectweb.asm.Type +import org.objectweb.asm.tree.LabelNode + +trait Value : org.objectweb.asm.tree.analysis.Value { + val asmType: Type + val valid: Boolean + override fun getSize(): Int = asmType.getSize() + + override fun toString(): String +} + +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" +} + +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" +} + +abstract class AbstractValueBase( + override val asmType: Type +) : Value { + override val valid = true + abstract val value: V + + 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() + } +} + +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(asmType: Type): AbstractValueBase(asmType) { + override var value: Any? = null +} + +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) +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() = (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(): 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 + } + +fun T?.checkNull(): T { + if (this == null) { + throwEvalException(NullPointerException()) + } + return this +} + +fun throwEvalException(e: Throwable): Nothing { + throw ThrownFromEvalException(e) +} \ No newline at end of file diff --git a/eval4j/test/org/jetbrains/eval4j/jdi/test/Debugee.java b/eval4j/test/org/jetbrains/eval4j/jdi/test/Debugee.java new file mode 100644 index 00000000000..0f5241b7cc2 --- /dev/null +++ b/eval4j/test/org/jetbrains/eval4j/jdi/test/Debugee.java @@ -0,0 +1,9 @@ +package org.jetbrains.eval4j.jdi.test; + +public class Debugee { + public static void main(String[] args) { + // BREAKPOINT + Runtime.getRuntime(); + System.out.println("hi"); + } +} diff --git a/eval4j/test/org/jetbrains/eval4j/jdi/test/jdiTest.kt b/eval4j/test/org/jetbrains/eval4j/jdi/test/jdiTest.kt new file mode 100644 index 00000000000..48dff47a059 --- /dev/null +++ b/eval4j/test/org/jetbrains/eval4j/jdi/test/jdiTest.kt @@ -0,0 +1,132 @@ +package org.jetbrains.eval4j.jdi.test + +import org.jetbrains.eval4j.* +import com.sun.jdi +import junit.framework.TestSuite +import org.jetbrains.eval4j.test.buildTestSuite +import junit.framework.TestCase +import org.jetbrains.eval4j.interpreterLoop +import org.junit.Assert.* +import java.util.concurrent.CountDownLatch +import java.util.concurrent.atomic.AtomicInteger +import org.jetbrains.eval4j.ExceptionThrown +import org.jetbrains.eval4j.MethodDescription +import org.jetbrains.eval4j.ValueReturned +import org.jetbrains.eval4j.jdi.* + +val DEBUGEE_CLASS = javaClass() + +fun suite(): TestSuite { + val connectors = jdi.Bootstrap.virtualMachineManager().launchingConnectors() + val connector = connectors[0] + println("Using connector $connector") + + val connectorArgs = connector.defaultArguments() + + val debugeeName = DEBUGEE_CLASS.getName() + println("Debugee name: $debugeeName") + connectorArgs["main"]!!.setValue(debugeeName) + connectorArgs["options"]!!.setValue("-classpath out/production/eval4j:out/test/eval4j") + val vm = connector.launch(connectorArgs)!! + + val req = vm.eventRequestManager().createClassPrepareRequest() + req.addClassFilter("*.Debugee") + req.enable() + + val latch = CountDownLatch(1) + var classLoader : jdi.ClassLoaderReference? = null + var thread : jdi.ThreadReference? = null + + Thread { + val eventQueue = vm.eventQueue() + @mainLoop while (true) { + val eventSet = eventQueue.remove() + for (event in eventSet.eventIterator()) { + when (event) { + is jdi.event.ClassPrepareEvent -> { + val _class = event.referenceType()!! + if (_class.name() == debugeeName) { + for (l in _class.allLineLocations()) { + if (l.method().name() == "main") { + classLoader = l.method().declaringType().classLoader() + val breakpointRequest = vm.eventRequestManager().createBreakpointRequest(l) + breakpointRequest.enable() + println("Breakpoint: $breakpointRequest") + vm.resume() + break + } + } + } + } + is jdi.event.BreakpointEvent -> { + println("Suspended at: " + event.location()) + + thread = event.thread() + latch.countDown() + + break @mainLoop + } + else -> {} + } + } + } + }.start() + + vm.resume() + + latch.await() + + var remainingTests = AtomicInteger(0) + + val suite = buildTestSuite { + methodNode, ownerClass, expected -> + remainingTests.incrementAndGet() + object : TestCase("test" + methodNode.name.capitalize()) { + + override fun runTest() { + val eval = JDIEval( + vm, classLoader!!, thread!! + ) + val value = interpreterLoop( + methodNode, + makeInitialFrame(methodNode, listOf()), + eval + ) + + fun jdi.ObjectReference?.callToString(): String? { + if (this == null) return "null" + return (eval.invokeMethod( + this.asValue(), + MethodDescription( + "java/lang/Object", + "toString", + "()Ljava/lang/String;", + false + ), + listOf()).jdiObj as jdi.StringReference).value() + + } + + try { + if (value is ExceptionThrown) { + val str = value.exception.jdiObj.callToString() + System.err.println("Exception: $str") + } + + if (expected is ValueReturned && value is ValueReturned && value.result is ObjectValue) { + assertEquals(expected.result.obj.toString(), value.result.jdiObj.callToString()) + } + else { + assertEquals(expected, value) + } + } + finally { + if (remainingTests.decrementAndGet() == 0) vm.resume() + } + + } + } + } + + return suite +} \ No newline at end of file diff --git a/eval4j/test/org/jetbrains/eval4j/test/ArrayHelper.java b/eval4j/test/org/jetbrains/eval4j/test/ArrayHelper.java new file mode 100644 index 00000000000..cf816d4ebf4 --- /dev/null +++ b/eval4j/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/eval4j/test/org/jetbrains/eval4j/test/TestData.java b/eval4j/test/org/jetbrains/eval4j/test/TestData.java new file mode 100644 index 00000000000..1b2cf2310d3 --- /dev/null +++ b/eval4j/test/org/jetbrains/eval4j/test/TestData.java @@ -0,0 +1,462 @@ +package org.jetbrains.eval4j.test; + +class TestData { + static void returnVoid() { + } + + static boolean returnBoolean() { + return true; + } + + static byte returnByte() { + return 2; + } + + static short returnShort() { + return 2; + } + + static char returnChar() { + return '2'; + } + + static int returnInt() { + return 2; + } + + static long returnLong() { + return 2; + } + + static float returnFloat() { + return 2.0f; + } + + static double returnDouble() { + return 2.0d; + } + + static Object returnNull() { + return null; + } + + static String returnString() { + return "str"; + } + + static Object returnStringAsObject() { + return "str"; + } + + 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; + } + } + + 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 simpleFinally() { + int i = 5; + try { + return i; + } + finally { + i = 3; + } + } + + static int simpleFinallyWithReturn() { + int i = 5; + try { + return i; + } + finally { + return 3; + } + } + + static int simpleFinallyWithContinueInLoop() { + int i = 5; + while (true) { + try { + if (i % 2 == 0) continue; + if (i > 10) return i; + } + finally { + i++; + } + } + } + + static int simpleFinallyWithBreakInLoop() { + int i = 5; + while (true) { + try { + if (i % 2 == 0) break; + } + finally { + i++; + } + } + return i; + } + + static Object call() { + return Integer.valueOf(1); + } + + static Object callWithObject() { + return String.valueOf("str"); + } + + static Object getStaticField() { + return C.FOO; + } + + static int FIELD = 0; + + static int putStaticField() { + FIELD = 5; + int f1 = FIELD; + FIELD = 6; + int f2 = FIELD; + return f2 + f1; + } + + static class C { + static String FOO = "FOO"; + + int y = 15; + + C(int y) { + this.y = y; + } + + C() {} + + int getY() { + return y; + } + + static C newC() { + return new C(); + } + + static void throwException() { + throw new RuntimeException(); + } + } + + static int getInstanceField() { + return C.newC().y; + } + + static int putInstanceField() { + C c = C.newC(); + c.y = 5; + int f1 = c.y; + c.y = 6; + int f2 = c.y; + return f1 + f2; + } + + static int instanceMethod() { + return C.newC().getY(); + } + + static int constructorCallNoArgs() { + return new C().y; + } + + 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; + } + + 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; + } + + 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/eval4j/test/org/jetbrains/eval4j/test/main.kt b/eval4j/test/org/jetbrains/eval4j/test/main.kt new file mode 100644 index 00000000000..0b48bcbcfed --- /dev/null +++ b/eval4j/test/org/jetbrains/eval4j/test/main.kt @@ -0,0 +1,356 @@ +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.Frame + +fun suite(): TestSuite = buildTestSuite { + methodNode, ownerClass, expected -> + object : TestCase("test" + methodNode.name.capitalize()) { + + override fun runTest() { + val value = interpreterLoop( + methodNode, + initFrame( + ownerClass.getInternalName(), + methodNode + ), + REFLECTION_EVAL + ) + + assertEquals(expected, value) + } + } +} + +fun Class<*>.getInternalName(): String = Type.getType(this).getInternalName() + +fun initFrame( + owner: String, + 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++, makeNotInitializedValue(ctype)) + } + + val args = Type.getArgumentTypes(m.desc) + for (i in 0..args.size - 1) { + current.setLocal(local++, makeNotInitializedValue(args[i])) + if (args[i].getSize() == 2) { + current.setLocal(local++, NOT_A_VALUE) + } + } + + while (local < m.maxLocals) { + current.setLocal(local++, NOT_A_VALUE) + } + + return current +} + +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 { + return ObjectValue(findClass(classType), Type.getType(javaClass>())) + } + + override fun loadString(str: String): Value = ObjectValue(str, Type.getType(javaClass())) + + override fun newInstance(classType: Type): Value { + return NewObjectValue(classType) + } + + override fun isInstanceOf(value: Value, targetType: Type): Boolean { + val _class = findClass(targetType) + return _class.isInstance(value.obj) + } + + [suppress("UNCHECKED_CAST")] + override fun newArray(arrayType: Type, size: Int): Value { + return ObjectValue(JArray.newInstance(findClass(arrayType).getComponentType() as Class, size), arrayType) + } + + override fun newMultiDimensionalArray(arrayType: Type, dimensionSizes: List): Value { + return ObjectValue(ArrayHelper.newMultiArray(findClass(arrayType.getElementType()), *dimensionSizes.copyToArray()), arrayType) + } + + override fun getArrayLength(array: Value): Value { + return int(JArray.getLength(array.obj.checkNull())) + } + + override fun getArrayElement(array: Value, index: Value): Value { + 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) { + 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 { + try { + try { + return f() + } + catch (ite: InvocationTargetException) { + throw ite.getCause() ?: ite + } + } + catch (e: Throwable) { + throw ThrownFromEvaluatedCodeException(ObjectValue(e, Type.getType(e.javaClass))) + } + } + + override fun getStaticField(fieldDesc: FieldDescription): Value { + val field = findStaticField(fieldDesc) + + val result = mayThrow {field.get(null)} + return objectToValue(result, fieldDesc.fieldType) + } + + override fun setStaticField(fieldDesc: FieldDescription, newValue: Value) { + val field = findStaticField(fieldDesc) + val obj = newValue.obj + mayThrow {field.set(null, obj)} + } + + fun findStaticField(fieldDesc: FieldDescription): Field { + assertTrue(fieldDesc.isStatic) + val field = findClass(fieldDesc).findField(fieldDesc) + assertNotNull("Field not found: $fieldDesc", field) + 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 method = findClass(methodDesc).findMethod(methodDesc) + assertNotNull("Method not found: $methodDesc", method) + 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(Type.getObjectType(memberDesc.ownerInternalName)) + + 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 { + val obj = instance.obj.checkNull() + val field = findInstanceField(obj, fieldDesc) + + return objectToValue(mayThrow {field.get(obj)}, fieldDesc.fieldType) + } + + override fun setField(instance: Value, fieldDesc: FieldDescription, newValue: Value) { + val obj = instance.obj.checkNull() + val field = findInstanceField(obj, fieldDesc) + + val newObj = newValue.obj + mayThrow {field.set(obj, newObj)} + } + + fun findInstanceField(obj: Any, fieldDesc: FieldDescription): Field { + val _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 { + if (invokespecial) { + if (methodDesc.name == "") { + // Constructor call + [suppress("UNCHECKED_CAST")] + 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() + val result = mayThrow {ctor!!.newInstance(*args)} + instance.value = result + return objectToValue(result, instance.asmType) + } + else { + // TODO + throw UnsupportedOperationException("invokespecial is not suported yet") + } + } + val obj = instance.obj.checkNull() + val method = obj.javaClass.findMethod(methodDesc) + assertNotNull("Method not found: $methodDesc", method) + 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(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")] +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 +} + +[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 + + 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()!!) +} + +[suppress("UNCHECKED_CAST")] +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) + 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 diff --git a/eval4j/test/org/jetbrains/eval4j/test/suiteBuilder.kt b/eval4j/test/org/jetbrains/eval4j/test/suiteBuilder.kt new file mode 100644 index 00000000000..8b26aa120b8 --- /dev/null +++ b/eval4j/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