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
}