diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/RawInstListBuilder.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/RawInstListBuilder.kt index 945f2e3ee..27e8c7d94 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/RawInstListBuilder.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/RawInstListBuilder.kt @@ -16,122 +16,18 @@ package org.jacodb.impl.cfg -import kotlinx.collections.immutable.PersistentList -import kotlinx.collections.immutable.PersistentMap -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toPersistentList -import kotlinx.collections.immutable.toPersistentMap +import kotlinx.collections.immutable.* import org.jacodb.api.JcMethod import org.jacodb.api.PredefinedPrimitives import org.jacodb.api.TypeName -import org.jacodb.api.cfg.BsmArg -import org.jacodb.api.cfg.BsmDoubleArg -import org.jacodb.api.cfg.BsmFloatArg -import org.jacodb.api.cfg.BsmHandle -import org.jacodb.api.cfg.BsmIntArg -import org.jacodb.api.cfg.BsmLongArg -import org.jacodb.api.cfg.BsmMethodTypeArg -import org.jacodb.api.cfg.BsmStringArg -import org.jacodb.api.cfg.BsmTypeArg -import org.jacodb.api.cfg.JcRawAddExpr -import org.jacodb.api.cfg.JcRawAndExpr -import org.jacodb.api.cfg.JcRawArgument -import org.jacodb.api.cfg.JcRawArrayAccess -import org.jacodb.api.cfg.JcRawAssignInst -import org.jacodb.api.cfg.JcRawCallExpr -import org.jacodb.api.cfg.JcRawCallInst -import org.jacodb.api.cfg.JcRawCastExpr -import org.jacodb.api.cfg.JcRawCatchEntry -import org.jacodb.api.cfg.JcRawCatchInst -import org.jacodb.api.cfg.JcRawClassConstant -import org.jacodb.api.cfg.JcRawCmpExpr -import org.jacodb.api.cfg.JcRawCmpgExpr -import org.jacodb.api.cfg.JcRawCmplExpr -import org.jacodb.api.cfg.JcRawDivExpr -import org.jacodb.api.cfg.JcRawDynamicCallExpr -import org.jacodb.api.cfg.JcRawEnterMonitorInst -import org.jacodb.api.cfg.JcRawEqExpr -import org.jacodb.api.cfg.JcRawExitMonitorInst -import org.jacodb.api.cfg.JcRawFieldRef -import org.jacodb.api.cfg.JcRawGeExpr -import org.jacodb.api.cfg.JcRawGotoInst -import org.jacodb.api.cfg.JcRawGtExpr -import org.jacodb.api.cfg.JcRawIfInst -import org.jacodb.api.cfg.JcRawInst -import org.jacodb.api.cfg.JcRawInstanceOfExpr -import org.jacodb.api.cfg.JcRawInterfaceCallExpr -import org.jacodb.api.cfg.JcRawLabelInst -import org.jacodb.api.cfg.JcRawLabelRef -import org.jacodb.api.cfg.JcRawLeExpr -import org.jacodb.api.cfg.JcRawLengthExpr -import org.jacodb.api.cfg.JcRawLineNumberInst -import org.jacodb.api.cfg.JcRawLocalVar -import org.jacodb.api.cfg.JcRawLtExpr -import org.jacodb.api.cfg.JcRawMethodConstant -import org.jacodb.api.cfg.JcRawMethodType -import org.jacodb.api.cfg.JcRawMulExpr -import org.jacodb.api.cfg.JcRawNegExpr -import org.jacodb.api.cfg.JcRawNeqExpr -import org.jacodb.api.cfg.JcRawNewArrayExpr -import org.jacodb.api.cfg.JcRawNewExpr -import org.jacodb.api.cfg.JcRawNullConstant -import org.jacodb.api.cfg.JcRawOrExpr -import org.jacodb.api.cfg.JcRawRemExpr -import org.jacodb.api.cfg.JcRawReturnInst -import org.jacodb.api.cfg.JcRawShlExpr -import org.jacodb.api.cfg.JcRawShrExpr -import org.jacodb.api.cfg.JcRawSimpleValue -import org.jacodb.api.cfg.JcRawSpecialCallExpr -import org.jacodb.api.cfg.JcRawStaticCallExpr -import org.jacodb.api.cfg.JcRawStringConstant -import org.jacodb.api.cfg.JcRawSubExpr -import org.jacodb.api.cfg.JcRawSwitchInst -import org.jacodb.api.cfg.JcRawThis -import org.jacodb.api.cfg.JcRawThrowInst -import org.jacodb.api.cfg.JcRawUshrExpr -import org.jacodb.api.cfg.JcRawValue -import org.jacodb.api.cfg.JcRawVirtualCallExpr -import org.jacodb.api.cfg.JcRawXorExpr -import org.jacodb.impl.cfg.util.CLASS_CLASS -import org.jacodb.impl.cfg.util.ExprMapper -import org.jacodb.impl.cfg.util.METHOD_HANDLES_CLASS -import org.jacodb.impl.cfg.util.METHOD_HANDLES_LOOKUP_CLASS -import org.jacodb.impl.cfg.util.METHOD_HANDLE_CLASS -import org.jacodb.impl.cfg.util.METHOD_TYPE_CLASS -import org.jacodb.impl.cfg.util.NULL -import org.jacodb.impl.cfg.util.OBJECT_CLASS -import org.jacodb.impl.cfg.util.STRING_CLASS -import org.jacodb.impl.cfg.util.THROWABLE_CLASS -import org.jacodb.impl.cfg.util.asArray -import org.jacodb.impl.cfg.util.elementType -import org.jacodb.impl.cfg.util.isArray -import org.jacodb.impl.cfg.util.isDWord -import org.jacodb.impl.cfg.util.isPrimitive -import org.jacodb.impl.cfg.util.typeName +import org.jacodb.api.cfg.* +import org.jacodb.impl.cfg.util.* import org.jacodb.impl.types.TypeNameImpl import org.objectweb.asm.ConstantDynamic 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.FrameNode -import org.objectweb.asm.tree.IincInsnNode -import org.objectweb.asm.tree.InsnNode -import org.objectweb.asm.tree.IntInsnNode -import org.objectweb.asm.tree.InvokeDynamicInsnNode -import org.objectweb.asm.tree.JumpInsnNode -import org.objectweb.asm.tree.LabelNode -import org.objectweb.asm.tree.LdcInsnNode -import org.objectweb.asm.tree.LineNumberNode -import org.objectweb.asm.tree.LookupSwitchInsnNode -import org.objectweb.asm.tree.MethodInsnNode -import org.objectweb.asm.tree.MethodNode -import org.objectweb.asm.tree.MultiANewArrayInsnNode -import org.objectweb.asm.tree.TableSwitchInsnNode -import org.objectweb.asm.tree.TryCatchBlockNode -import org.objectweb.asm.tree.TypeInsnNode -import org.objectweb.asm.tree.VarInsnNode +import org.objectweb.asm.tree.* import java.util.* private fun Int.toPrimitiveType(): TypeName = when (this) { @@ -292,6 +188,8 @@ class RawInstListBuilder( private val laterAssignments = identityMap>() private val laterStackAssignments = identityMap>() private val localTypeRefinement = identityMap() + private val postfixInstructions = hashMapOf() + private var labelCounter = 0 private var localCounter = 0 private var argCounter = 0 @@ -354,9 +252,9 @@ class RawInstListBuilder( for ((variable, value) in assignments) { if (value != frame[variable]) { if (insn.isBranchingInst || insn.isTerminateInst) { - insnList.add(insnList.lastIndex, JcRawAssignInst(method, value, frame[variable]!!)) + insnList.addInst(insn, JcRawAssignInst(method, value, frame[variable]!!), insnList.lastIndex) } else { - insnList += JcRawAssignInst(method, value, frame[variable]!!) + insnList.addInst(insn, JcRawAssignInst(method, value, frame[variable]!!)) } } } @@ -367,9 +265,9 @@ class RawInstListBuilder( for ((variable, value) in assignments) { if (value != frame.stack[variable]) { if (insn.isBranchingInst || insn.isTerminateInst) { - insnList.add(insnList.lastIndex, JcRawAssignInst(method, value, frame.stack[variable])) + insnList.addInst(insn, JcRawAssignInst(method, value, frame.stack[variable]), insnList.lastIndex) } else { - insnList += JcRawAssignInst(method, value, frame.stack[variable]) + insnList.addInst(insn, JcRawAssignInst(method, value, frame.stack[variable])) } } } @@ -390,11 +288,11 @@ class RawInstListBuilder( is LabelNode -> labelRef(insn) else -> { val newLabel = nextLabel() - instructionList(insn).add(0, newLabel) + addInstruction(insn, newLabel, 0) newLabel.ref } } - instructionList(predecessor).add(JcRawGotoInst(method, label)) + addInstruction(predecessor, JcRawGotoInst(method, label)) } } } @@ -504,6 +402,29 @@ class RawInstListBuilder( private fun instructionList(insn: AbstractInsnNode) = instructions.getOrPut(insn, ::mutableListOf) + private fun addInstruction(insn: AbstractInsnNode, inst: JcRawInst, index: Int? = null) { + instructionList(insn).addInst(insn, inst, index) + } + + private fun MutableList.addInst(node: AbstractInsnNode, inst: JcRawInst, index: Int? = null) { + if (index != null) { + add(index, inst) + } else { + add(inst) + } + if (postfixInstructions.isNotEmpty()) { + when { + node.isBranchingInst -> postfixInstructions.forEach { + instructionList(node).add(0, it.value) + } + + inst !is JcRawReturnInst -> postfixInstructions.forEach { + instructionList(node).add(it.value) + } + } + postfixInstructions.clear() + } + } private fun nextRegister(typeName: TypeName): JcRawValue { return JcRawLocalVar("%${localCounter++}", typeName) @@ -625,7 +546,7 @@ class RawInstListBuilder( val read = JcRawArrayAccess(arrayRef, index, arrayRef.typeName.elementType()) val assignment = nextRegister(read.typeName) - instructionList(insn).add(JcRawAssignInst(method, assignment, read)) + addInstruction(insn, JcRawAssignInst(method, assignment, read)) push(assignment) } @@ -633,11 +554,11 @@ class RawInstListBuilder( val value = pop() val index = pop() val arrayRef = pop() - instructionList(insn) += JcRawAssignInst( + addInstruction(insn, JcRawAssignInst( method, JcRawArrayAccess(arrayRef, index, arrayRef.typeName.elementType()), value - ) + )) } private fun buildPop(insn: InsnNode) { @@ -777,7 +698,7 @@ class RawInstListBuilder( else -> error("Unknown binary opcode: $opcode") } val assignment = nextRegister(resolvedType) - instructionList(insn) += JcRawAssignInst(method, assignment, expr) + addInstruction(insn, JcRawAssignInst(method, assignment, expr)) push(assignment) } @@ -806,7 +727,7 @@ class RawInstListBuilder( else -> error("Unknown unary opcode $opcode") } val assignment = nextRegister(expr.typeName) - instructionList(insn) += JcRawAssignInst(method, assignment, expr) + addInstruction(insn, JcRawAssignInst(method, assignment, expr)) push(assignment) } @@ -823,7 +744,7 @@ class RawInstListBuilder( else -> error("Unknown cast opcode $opcode") } val assignment = nextRegister(targetType) - instructionList(insn) += JcRawAssignInst(method, assignment, JcRawCastExpr(targetType, operand)) + addInstruction(insn, JcRawAssignInst(method, assignment, JcRawCastExpr(targetType, operand))) push(assignment) } @@ -837,33 +758,33 @@ class RawInstListBuilder( else -> error("Unknown cmp opcode $opcode") } val assignment = nextRegister(PredefinedPrimitives.Int.typeName()) - instructionList(insn) += JcRawAssignInst(method, assignment, expr) + addInstruction(insn, JcRawAssignInst(method, assignment, expr)) push(assignment) } private fun buildReturn(insn: InsnNode) { - instructionList(insn) += when (val opcode = insn.opcode) { + addInstruction(insn, when (val opcode = insn.opcode) { Opcodes.RETURN -> JcRawReturnInst(method, null) in Opcodes.IRETURN..Opcodes.ARETURN -> JcRawReturnInst(method, pop()) else -> error("Unknown return opcode: $opcode") - } + }) } private fun buildMonitor(insn: InsnNode) { val monitor = pop() as JcRawSimpleValue - instructionList(insn) += when (val opcode = insn.opcode) { + addInstruction(insn, when (val opcode = insn.opcode) { Opcodes.MONITORENTER -> { JcRawEnterMonitorInst(method, monitor) } Opcodes.MONITOREXIT -> JcRawExitMonitorInst(method, monitor) else -> error("Unknown monitor opcode $opcode") - } + }) } private fun buildThrow(insn: InsnNode) { val throwable = pop() - instructionList(insn) += JcRawThrowInst(method, throwable) + addInstruction(insn, JcRawThrowInst(method, throwable)) } private fun buildFieldInsnNode(insnNode: FieldInsnNode) { @@ -874,7 +795,7 @@ class RawInstListBuilder( Opcodes.GETFIELD -> { val assignment = nextRegister(fieldType) val field = JcRawFieldRef(pop(), declaringClass, fieldName, fieldType) - instructionList(insnNode).add(JcRawAssignInst(method, assignment, field)) + addInstruction(insnNode, JcRawAssignInst(method, assignment, field)) push(assignment) } @@ -882,20 +803,20 @@ class RawInstListBuilder( val value = pop() val instance = pop() val fieldRef = JcRawFieldRef(instance, declaringClass, fieldName, fieldType) - instructionList(insnNode) += JcRawAssignInst(method, fieldRef, value) + addInstruction(insnNode, JcRawAssignInst(method, fieldRef, value)) } Opcodes.GETSTATIC -> { val assignment = nextRegister(fieldType) val field = JcRawFieldRef(declaringClass, fieldName, fieldType) - instructionList(insnNode).add(JcRawAssignInst(method, assignment, field)) + addInstruction(insnNode, JcRawAssignInst(method, assignment, field)) push(assignment) } Opcodes.PUTSTATIC -> { val value = pop() val fieldRef = JcRawFieldRef(declaringClass, fieldName, fieldType) - instructionList(insnNode) += JcRawAssignInst(method, fieldRef, value) + addInstruction(insnNode, JcRawAssignInst(method, fieldRef, value)) } } } @@ -958,11 +879,10 @@ class RawInstListBuilder( val assignment = nextRegister(type) for ((node, frame) in predFrames) { if (frame != null) { - val instList = instructionList(node) if (node.isBranchingInst) { - instList.add(0, JcRawAssignInst(method, assignment, frame[variable]!!)) + addInstruction(node, JcRawAssignInst(method, assignment, frame[variable]!!), 0) } else { - instList.add(JcRawAssignInst(method, assignment, frame[variable]!!)) + addInstruction(node, JcRawAssignInst(method, assignment, frame[variable]!!)) } } else { laterAssignments.getOrPut(node, ::mutableMapOf)[variable] = assignment @@ -1028,11 +948,10 @@ class RawInstListBuilder( val assignment = nextRegister(type) for ((node, frame) in predFrames) { if (frame != null) { - val instList = instructionList(node) if (node.isBranchingInst) { - instList.add(0, JcRawAssignInst(method, assignment, frame.stack[variable])) + addInstruction(node, JcRawAssignInst(method, assignment, frame.stack[variable]), 0) } else { - instList.add(JcRawAssignInst(method, assignment, frame.stack[variable])) + addInstruction(node, JcRawAssignInst(method, assignment, frame.stack[variable])) } } else { laterStackAssignments.getOrPut(node, ::mutableMapOf)[variable] = assignment @@ -1084,22 +1003,27 @@ class RawInstListBuilder( ) } - instructionList(insnNode) += JcRawCatchInst( + + val catchInst = JcRawCatchInst( method, throwable, labelRef(currentEntry), entries ) + addInstruction(currentEntry, catchInst, index = 1) + push(throwable) } } private fun buildIincInsnNode(insnNode: IincInsnNode) { - val local = local(insnNode.`var`) - val add = JcRawAddExpr(local.typeName, local, JcRawInt(insnNode.incr)) - instructionList(insnNode) += JcRawAssignInst(method, local, add) - local(insnNode.`var`, local, insnNode) + val variable = insnNode.`var` + val local = local(variable) + postfixInstructions[variable] = JcRawAssignInst(method, local, + JcRawAddExpr(local.typeName, local, JcRawInt(insnNode.incr)) + ) + local(variable, local, insnNode) } private fun buildIntInsnNode(insnNode: IntInsnNode) { @@ -1110,7 +1034,7 @@ class RawInstListBuilder( Opcodes.NEWARRAY -> { val expr = JcRawNewArrayExpr(operand.toPrimitiveType().asArray(), pop()) val assignment = nextRegister(expr.typeName) - instructionList(insnNode) += JcRawAssignInst(method, assignment, expr) + addInstruction(insnNode, JcRawAssignInst(method, assignment, expr)) push(assignment) } @@ -1158,10 +1082,10 @@ class RawInstListBuilder( args, ) if (Type.getReturnType(desc) == Type.VOID_TYPE) { - instructionList(insnNode) += JcRawCallInst(method, expr) + addInstruction(insnNode, JcRawCallInst(method, expr)) } else { val result = nextRegister(Type.getReturnType(desc).descriptor.typeName()) - instructionList(insnNode) += JcRawAssignInst(method, result, expr) + addInstruction(insnNode, JcRawAssignInst(method, result, expr)) push(result) } } @@ -1169,7 +1093,7 @@ class RawInstListBuilder( private fun buildJumpInsnNode(insnNode: JumpInsnNode) { val target = labelRef(insnNode.label) when (val opcode = insnNode.opcode) { - Opcodes.GOTO -> instructionList(insnNode) += JcRawGotoInst(method, target) + Opcodes.GOTO -> addInstruction(insnNode, JcRawGotoInst(method, target)) else -> { val falseTarget = (insnNode.next as? LabelNode)?.let { label(it) } ?: nextLabel() val rhv = pop() @@ -1193,9 +1117,9 @@ class RawInstListBuilder( Opcodes.IF_ACMPNE -> JcRawNeqExpr(boolTypeName, pop(), rhv) else -> error("Unknown jump opcode $opcode") } - instructionList(insnNode) += JcRawIfInst(method, expr, target, falseTarget.ref) + addInstruction(insnNode, JcRawIfInst(method, expr, target, falseTarget.ref)) if (insnNode.next !is LabelNode) { - instructionList(insnNode) += falseTarget + addInstruction(insnNode, falseTarget) } } } @@ -1231,7 +1155,7 @@ class RawInstListBuilder( private fun buildLabelNode(insnNode: LabelNode) { val labelInst = label(insnNode) - instructionList(insnNode) += labelInst + addInstruction(insnNode, labelInst) val predecessors = predecessors.getOrDefault(insnNode, emptySet()).filter { !deadInstructions.contains(it) } val predecessorFrames = predecessors.mapNotNull { frames[it] } if (predecessorFrames.size == 1) { @@ -1248,7 +1172,7 @@ class RawInstListBuilder( } private fun buildLineNumberNode(insnNode: LineNumberNode) { - instructionList(insnNode) += JcRawLineNumberInst(method, insnNode.line, labelRef(insnNode.start)) + addInstruction(insnNode, JcRawLineNumberInst(method, insnNode.line, labelRef(insnNode.start))) } private fun ldcValue(cst: Any): JcRawValue { @@ -1282,7 +1206,7 @@ class RawInstListBuilder( is String -> push(JcRawStringConstant(cst, STRING_CLASS.typeName())) is Type -> { val assignment = nextRegister(CLASS_CLASS.typeName()) - instructionList(insnNode) += JcRawAssignInst( + addInstruction(insnNode, JcRawAssignInst( method, assignment, when (cst.sort) { @@ -1294,17 +1218,17 @@ class RawInstListBuilder( else -> ldcValue(cst) } - ) + )) push(assignment) } is Handle -> { val assignment = nextRegister(CLASS_CLASS.typeName()) - instructionList(insnNode) += JcRawAssignInst( + addInstruction(insnNode, JcRawAssignInst( method, assignment, ldcValue(cst) - ) + )) push(assignment) } @@ -1329,7 +1253,7 @@ class RawInstListBuilder( else -> { val lookupAssignment = nextRegister(METHOD_HANDLES_LOOKUP_CLASS.typeName()) - instructionList(insnNode) += JcRawAssignInst( + addInstruction(insnNode, JcRawAssignInst( method, lookupAssignment, JcRawStaticCallExpr( @@ -1339,7 +1263,7 @@ class RawInstListBuilder( METHOD_HANDLES_LOOKUP_CLASS.typeName(), emptyList() ) - ) + )) JcRawStaticCallExpr( methodHande.owner.typeName(), methodHande.name, @@ -1353,7 +1277,7 @@ class RawInstListBuilder( ) } } - instructionList(insnNode) += JcRawAssignInst(method, assignment, methodCall) + addInstruction(insnNode, JcRawAssignInst(method, assignment, methodCall)) push(assignment) } @@ -1367,7 +1291,7 @@ class RawInstListBuilder( val branches = insnNode.keys .zip(insnNode.labels) .associate { (JcRawInt(it.first) as JcRawValue) to labelRef(it.second) } - instructionList(insnNode) += JcRawSwitchInst(method, key, branches, default) + addInstruction(insnNode, JcRawSwitchInst(method, key, branches, default)) } private fun buildMethodInsnNode(insnNode: MethodInsnNode) { @@ -1425,10 +1349,10 @@ class RawInstListBuilder( } } if (Type.getReturnType(insnNode.desc) == Type.VOID_TYPE) { - instructionList(insnNode) += JcRawCallInst(method, expr) + addInstruction(insnNode, JcRawCallInst(method, expr)) } else { val result = nextRegister(Type.getReturnType(insnNode.desc).descriptor.typeName()) - instructionList(insnNode) += JcRawAssignInst(method, result, expr) + addInstruction(insnNode, JcRawAssignInst(method, result, expr)) push(result) } } @@ -1440,7 +1364,7 @@ class RawInstListBuilder( } val expr = JcRawNewArrayExpr(insnNode.desc.typeName(), dimensions) val assignment = nextRegister(expr.typeName) - instructionList(insnNode) += JcRawAssignInst(method, assignment, expr) + addInstruction(insnNode, JcRawAssignInst(method, assignment, expr)) push(assignment) } @@ -1450,7 +1374,7 @@ class RawInstListBuilder( val branches = (insnNode.min..insnNode.max) .zip(insnNode.labels) .associate { (JcRawInt(it.first) as JcRawValue) to labelRef(it.second) } - instructionList(insnNode) += JcRawSwitchInst(method, index, branches, default) + addInstruction(insnNode, JcRawSwitchInst(method, index, branches, default)) } private fun buildTypeInsnNode(insnNode: TypeInsnNode) { @@ -1458,34 +1382,34 @@ class RawInstListBuilder( when (insnNode.opcode) { Opcodes.NEW -> { val assignment = nextRegister(type) - instructionList(insnNode) += JcRawAssignInst(method, assignment, JcRawNewExpr(type)) + addInstruction(insnNode, JcRawAssignInst(method, assignment, JcRawNewExpr(type))) push(assignment) } Opcodes.ANEWARRAY -> { val length = pop() val assignment = nextRegister(type.asArray()) - instructionList(insnNode) += JcRawAssignInst( + addInstruction(insnNode, JcRawAssignInst( method, assignment, JcRawNewArrayExpr(type.asArray(), length) - ) + )) push(assignment) } Opcodes.CHECKCAST -> { val assignment = nextRegister(type) - instructionList(insnNode) += JcRawAssignInst(method, assignment, JcRawCastExpr(type, pop())) + addInstruction(insnNode, JcRawAssignInst(method, assignment, JcRawCastExpr(type, pop()))) push(assignment) } Opcodes.INSTANCEOF -> { val assignment = nextRegister(PredefinedPrimitives.Boolean.typeName()) - instructionList(insnNode) += JcRawAssignInst( + addInstruction(insnNode, JcRawAssignInst( method, assignment, JcRawInstanceOfExpr(PredefinedPrimitives.Boolean.typeName(), pop(), type) - ) + )) push(assignment) } @@ -1494,14 +1418,21 @@ class RawInstListBuilder( } private fun buildVarInsnNode(insnNode: VarInsnNode) { + val variable = insnNode.`var` when (insnNode.opcode) { in Opcodes.ISTORE..Opcodes.ASTORE -> local( - insnNode.`var`, + variable, pop(), insnNode - )?.let { instructionList(insnNode).add(it) } + )?.let { addInstruction(insnNode, it) } - in Opcodes.ILOAD..Opcodes.ALOAD -> push(local(insnNode.`var`)) + in Opcodes.ILOAD..Opcodes.ALOAD -> { + push(local(variable)) + postfixInstructions[variable]?.let { + postfixInstructions.remove(variable) + instructionList(insnNode).add(it) // do not reuse `addInstruction` function here + } + } else -> error("Unknown opcode ${insnNode.opcode} in VarInsnNode") } } diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/Simplifier.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/Simplifier.kt index be1425ed1..c3f0854e5 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/Simplifier.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/Simplifier.kt @@ -17,19 +17,8 @@ package org.jacodb.impl.cfg import org.jacodb.api.JcClasspath -import org.jacodb.api.cfg.JcRawAssignInst -import org.jacodb.api.cfg.JcRawCatchInst -import org.jacodb.api.cfg.JcRawComplexValue -import org.jacodb.api.cfg.JcRawConstant -import org.jacodb.api.cfg.JcRawExpr -import org.jacodb.api.cfg.JcRawInst -import org.jacodb.api.cfg.JcRawLabelInst -import org.jacodb.api.cfg.JcRawLocalVar -import org.jacodb.api.cfg.JcRawNullConstant -import org.jacodb.api.cfg.JcRawSimpleValue -import org.jacodb.api.cfg.JcRawValue +import org.jacodb.api.cfg.* import org.jacodb.api.ext.cfg.applyAndGet -import org.jacodb.api.cfg.AbstractFullRawExprSetCollector import org.jacodb.impl.cfg.util.ExprMapper import org.jacodb.impl.cfg.util.InstructionFilter diff --git a/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/BaseInstructionsTest.kt b/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/BaseInstructionsTest.kt new file mode 100644 index 000000000..3052ee79c --- /dev/null +++ b/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/BaseInstructionsTest.kt @@ -0,0 +1,114 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.testing.cfg + +import kotlinx.coroutines.runBlocking +import org.jacodb.api.JcClassOrInterface +import org.jacodb.api.NoClassInClasspathException +import org.jacodb.api.cfg.applyAndGet +import org.jacodb.api.ext.isKotlin +import org.jacodb.api.ext.packageName +import org.jacodb.impl.bytecode.JcDatabaseClassWriter +import org.jacodb.impl.cfg.MethodNodeBuilder +import org.jacodb.impl.features.hierarchyExt +import org.jacodb.testing.BaseTest +import org.junit.jupiter.api.Assertions +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.util.CheckClassAdapter +import java.io.File +import java.net.URLClassLoader +import java.nio.file.Files +import java.nio.file.Paths + +abstract class BaseInstructionsTest : BaseTest() { + + private val target = Files.createTempDirectory("jcdb-temp") + + val ext = runBlocking { cp.hierarchyExt() } + + protected fun testClass(klass: JcClassOrInterface) { + testAndLoadClass(klass, false) + } + + protected fun testAndLoadClass(klass: JcClassOrInterface): Class<*> { + return testAndLoadClass(klass, true)!! + } + + private fun testAndLoadClass(klass: JcClassOrInterface, loadClass: Boolean): Class<*>? { + try { + val classNode = klass.asmNode() + classNode.methods = klass.declaredMethods.filter { it.enclosingClass == klass }.map { + if (it.isAbstract || it.name.contains("$\$forInline")) { + it.asmNode() + } else { + try { +// val oldBody = it.body() +// println() +// println("Old body: ${oldBody.print()}") + val instructionList = it.rawInstList + it.instList.forEachIndexed { index, inst -> + Assertions.assertEquals(index, inst.location.index, "indexes not matched for $it at $index") + } +// println("Instruction list: $instructionList") + val graph = it.flowGraph() + if (!it.enclosingClass.isKotlin) { + val methodMsg = "$it should have line number" + graph.instructions.forEach { + Assertions.assertTrue(it.lineNumber > 0, methodMsg) + } + } + graph.applyAndGet(OverridesResolver(ext)) {} + JcGraphChecker(it, graph).check() +// println("Graph: $graph") +// graph.view("/usr/bin/dot", "/usr/bin/firefox", false) +// graph.blockGraph().view("/usr/bin/dot", "/usr/bin/firefox") + val newBody = MethodNodeBuilder(it, instructionList).build() +// println("New body: ${newBody.print()}") +// println() + newBody + } catch (e: Exception) { + throw IllegalStateException("error handling $it", e) + } + + } + } + val cw = JcDatabaseClassWriter(cp, ClassWriter.COMPUTE_FRAMES) + val checker = CheckClassAdapter(cw) + try { + classNode.accept(checker) + } catch (ex: Throwable) { + println(ex) + } + val targetDir = target.resolve(klass.packageName.replace('.', '/')) + val targetFile = targetDir.resolve("${klass.simpleName}.class").toFile().also { + it.parentFile?.mkdirs() + } + targetFile.writeBytes(cw.toByteArray()) + if (loadClass) { + + val cp = listOf(target.toUri().toURL()) + System.getProperty("java.class.path") + .split(File.pathSeparatorChar) + .map { Paths.get(it).toUri().toURL() } + val allClassLoader = URLClassLoader(cp.toTypedArray(), null) + return allClassLoader.loadClass(klass.name) + } + } catch (e: NoClassInClasspathException) { + System.err.println(e.localizedMessage) + } + return null + } +} \ No newline at end of file diff --git a/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/IRTest.kt b/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/IRTest.kt index ab9459ce1..b7ee3757d 100644 --- a/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/IRTest.kt +++ b/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/IRTest.kt @@ -16,23 +16,25 @@ package org.jacodb.testing.cfg -import kotlinx.coroutines.runBlocking + import org.jacodb.api.* import org.jacodb.api.cfg.* -import org.jacodb.api.ext.* +import org.jacodb.api.ext.HierarchyExtension +import org.jacodb.api.ext.findClass +import org.jacodb.api.ext.toType import org.jacodb.impl.JcClasspathImpl import org.jacodb.impl.JcDatabaseImpl import org.jacodb.impl.bytecode.JcClassOrInterfaceImpl -import org.jacodb.impl.bytecode.JcDatabaseClassWriter import org.jacodb.impl.bytecode.JcMethodImpl -import org.jacodb.impl.cfg.* +import org.jacodb.impl.cfg.JcBlockGraphImpl +import org.jacodb.impl.cfg.JcInstListBuilder +import org.jacodb.impl.cfg.RawInstListBuilder +import org.jacodb.impl.cfg.Simplifier import org.jacodb.impl.cfg.util.ExprMapper import org.jacodb.impl.features.InMemoryHierarchy import org.jacodb.impl.features.classpaths.ClasspathCache import org.jacodb.impl.features.classpaths.StringConcatSimplifier -import org.jacodb.impl.features.hierarchyExt import org.jacodb.impl.fs.JarLocation -import org.jacodb.testing.BaseTest import org.jacodb.testing.WithDB import org.jacodb.testing.guavaLib import org.jacodb.testing.kotlinxCoroutines @@ -40,11 +42,7 @@ import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertDoesNotThrow -import org.objectweb.asm.ClassWriter -import org.objectweb.asm.util.CheckClassAdapter import java.io.File -import java.net.URLClassLoader -import java.nio.file.Files class OverridesResolver( private val hierarchyExtension: HierarchyExtension @@ -255,14 +253,10 @@ class JcGraphChecker(val method: JcMethod, val jcGraph: JcGraph) : JcInstVisitor } } -class IRTest : BaseTest() { +class IRTest : BaseInstructionsTest() { companion object : WithDB(InMemoryHierarchy, StringConcatSimplifier) - private val target = Files.createTempDirectory("jcdb-temp") - - private val ext = runBlocking { cp.hierarchyExt() } - @Test fun `get ir of simple method`() { testClass(cp.findClass()) @@ -285,7 +279,7 @@ class IRTest : BaseTest() { } - @Test + @Test fun `get ir of self`() { testClass(cp.findClass()) testClass(cp.findClass()) @@ -335,55 +329,4 @@ class IRTest : BaseTest() { } } - - private fun testClass(klass: JcClassOrInterface) = try { - val classNode = klass.asmNode() - classNode.methods = klass.declaredMethods.filter { it.enclosingClass == klass }.map { - if (it.isAbstract || it.name.contains("$\$forInline")) { - it.asmNode() - } else { - try { -// val oldBody = it.body() -// println() -// println("Old body: ${oldBody.print()}") - val instructionList = it.rawInstList - it.instList.forEachIndexed { index, inst -> - assertEquals(index, inst.location.index, "indexes not matched for $it at $index") - } -// println("Instruction list: $instructionList") - val graph = it.flowGraph() - if (!it.enclosingClass.isKotlin) { - graph.instructions.forEach { - assertTrue(it.lineNumber > 0, "$it should have line number") - } - } - graph.applyAndGet(OverridesResolver(ext)) {} - JcGraphChecker(it, graph).check() -// println("Graph: $graph") -// graph.view("/usr/bin/dot", "/usr/bin/firefox", false) -// graph.blockGraph().view("/usr/bin/dot", "/usr/bin/firefox") - val newBody = MethodNodeBuilder(it, instructionList).build() -// println("New body: ${newBody.print()}") -// println() - newBody - } catch (e: Exception) { - throw IllegalStateException("error handling $it", e) - } - - } - } - val cw = JcDatabaseClassWriter(cp, ClassWriter.COMPUTE_FRAMES) - val checker = CheckClassAdapter(classNode) - classNode.accept(checker) - val targetDir = target.resolve(klass.packageName.replace('.', '/')) - val targetFile = targetDir.resolve("${klass.simpleName}.class").toFile().also { - it.parentFile?.mkdirs() - } - targetFile.writeBytes(cw.toByteArray()) - - val classloader = URLClassLoader(arrayOf(target.toUri().toURL())) - classloader.loadClass(klass.name) - } catch (e: NoClassInClasspathException) { - System.err.println(e.localizedMessage) - } } diff --git a/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/InstructionsTest.kt b/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/InstructionsTest.kt index 727c05495..19ad1e9c2 100644 --- a/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/InstructionsTest.kt +++ b/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/InstructionsTest.kt @@ -31,7 +31,6 @@ import org.jacodb.api.ext.cfg.values import org.jacodb.api.ext.findClass import org.jacodb.api.ext.humanReadableSignature import org.jacodb.api.ext.int -import org.jacodb.testing.BaseTest import org.jacodb.testing.Common import org.jacodb.testing.Common.CommonClass import org.jacodb.testing.WithDB @@ -51,7 +50,7 @@ import java.util.concurrent.ConcurrentHashMap import javax.activation.DataHandler -class InstructionsTest : BaseTest() { +class InstructionsTest : BaseInstructionsTest() { companion object : WithDB() @@ -267,6 +266,48 @@ class InstructionsTest : BaseTest() { } assertEquals("defaultMethod", callDefaultMethod.method.method.name) } + + + @Test + fun `iinc should work`() { + val clazz = cp.findClass() + + val javaClazz = testAndLoadClass(clazz) + val method = javaClazz.methods.first { it.name == "iinc" } + val res = method.invoke(null, 0) + assertEquals(0, res) + } + + @Test + fun `iinc arrayIntIdx should work`() { + val clazz = cp.findClass() + + val javaClazz = testAndLoadClass(clazz) + val method = javaClazz.methods.first { it.name == "iincArrayIntIdx" } + val res = method.invoke(null) + assertArrayEquals(intArrayOf(1, 0, 2), res as IntArray) + } + + @Test + fun `iinc arrayByteIdx should work`() { + val clazz = cp.findClass() + + val javaClazz = testAndLoadClass(clazz) + val method = javaClazz.methods.first { it.name == "iincArrayByteIdx" } + val res = method.invoke(null) + assertArrayEquals(intArrayOf(1, 0, 2), res as IntArray) + } + + @Test + fun `iinc for`() { + val clazz = cp.findClass() + + val javaClazz = testAndLoadClass(clazz) + val method = javaClazz.methods.first { it.name == "iincFor" } + val res = method.invoke(null) + assertArrayEquals(intArrayOf(0, 1, 2, 3, 4), res as IntArray) + } + } fun JcMethod.dumpInstructions(): String { diff --git a/jacodb-core/src/testFixtures/java/org/jacodb/testing/cfg/IRExamples.java b/jacodb-core/src/testFixtures/java/org/jacodb/testing/cfg/IRExamples.java index cb4070ab2..22c97f98a 100644 --- a/jacodb-core/src/testFixtures/java/org/jacodb/testing/cfg/IRExamples.java +++ b/jacodb-core/src/testFixtures/java/org/jacodb/testing/cfg/IRExamples.java @@ -136,9 +136,10 @@ public void initStringWithNull(String arg) { public int testArrays(String[] arg) { int index = 12; String x = arg[index]; - if(x != null) { + if (x != null) { return x.length(); } return -1; } + } diff --git a/jacodb-core/src/testFixtures/java/org/jacodb/testing/cfg/Incrementation.java b/jacodb-core/src/testFixtures/java/org/jacodb/testing/cfg/Incrementation.java new file mode 100644 index 000000000..e53a5e797 --- /dev/null +++ b/jacodb-core/src/testFixtures/java/org/jacodb/testing/cfg/Incrementation.java @@ -0,0 +1,49 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.testing.cfg; + +public class Incrementation { + + static public int iinc(int x) { + return x++; + } + + static public int[] iincArrayIntIdx() { + int[] arr = new int[3]; + int idx = 0; + arr[idx++] = 1; + arr[++idx] = 2; + return arr; + } + + static public int[] iincArrayByteIdx() { + int[] arr = new int[3]; + byte idx = 0; + arr[idx++] = 1; + arr[++idx] = 2; + return arr; + } + + static public int[] iincFor() { + int[] result = new int[5]; + for (int i = 0; i < 5; i++) { + result[i] = i; + } + return result; + } + +}