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 }