diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt index feb195ad5..3e4800614 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt @@ -88,7 +88,9 @@ abstract class BaseAnalysisTest : BaseTest() { // TODO: think about better assertions here assertEquals(expectedLocations.size, sinks.size) expectedLocations.forEach { expected -> - assertTrue(sinks.any { it.contains(expected) }) + assertTrue(sinks.any { it.contains(expected) }) { + "$expected unmatched in:\n${sinks.joinToString("\n")}" + } } } diff --git a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/NpeAnalysisTest.kt b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/NpeAnalysisTest.kt index 06267e623..01fec83a2 100644 --- a/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/NpeAnalysisTest.kt +++ b/jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/NpeAnalysisTest.kt @@ -60,7 +60,7 @@ class NpeAnalysisTest : BaseAnalysisTest() { @Test fun `analyze simple NPE`() { - testOneMethod("npeOnLength", listOf("%3 = %0.length()")) + testOneMethod("npeOnLength", listOf("%3 = x.length()")) } @Test @@ -72,7 +72,7 @@ class NpeAnalysisTest : BaseAnalysisTest() { fun `analyze NPE after fun with two exits`() { testOneMethod( "npeAfterTwoExits", - listOf("%4 = %0.length()", "%5 = %1.length()") + listOf("%4 = x.length()", "%5 = y.length()") ) } @@ -85,7 +85,7 @@ class NpeAnalysisTest : BaseAnalysisTest() { fun `consecutive NPEs handled properly`() { testOneMethod( "consecutiveNPEs", - listOf("%2 = arg$0.length()", "%4 = arg$0.length()") + listOf("a = x.length()", "c = x.length()") ) } @@ -93,7 +93,7 @@ class NpeAnalysisTest : BaseAnalysisTest() { fun `npe on virtual call when possible`() { testOneMethod( "possibleNPEOnVirtualCall", - listOf("%0 = arg\$0.length()") + listOf("%0 = x.length()") ) } @@ -107,7 +107,7 @@ class NpeAnalysisTest : BaseAnalysisTest() { @Test fun `basic test for NPE on fields`() { - testOneMethod("simpleNPEOnField", listOf("%8 = %6.length()")) + testOneMethod("simpleNPEOnField", listOf("len2 = second.length()")) } @Disabled("Flowdroid architecture not supported for async ifds yet") @@ -152,7 +152,7 @@ class NpeAnalysisTest : BaseAnalysisTest() { @Test fun `NPE on uninitialized array element dereferencing`() { - testOneMethod("simpleArrayNPE", listOf("%5 = %4.length()")) + testOneMethod("simpleArrayNPE", listOf("b = %4.length()")) } @Test @@ -174,7 +174,7 @@ class NpeAnalysisTest : BaseAnalysisTest() { @Test fun `dereferencing field of null object`() { - testOneMethod("npeOnFieldDeref", listOf("%1 = %0.field")) + testOneMethod("npeOnFieldDeref", listOf("s = a.field")) } @Test diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInst.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInst.kt index 74bedf74e..2ddc8fa25 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInst.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInst.kt @@ -811,8 +811,10 @@ interface JcLocal : JcSimpleValue { val name: String } +/** + * @param name isn't considered in `equals` and `hashcode` + */ data class JcArgument(val index: Int, override val name: String, override val type: JcType) : JcLocal { - companion object { @JvmStatic fun of(index: Int, name: String?, type: JcType): JcArgument { @@ -825,14 +827,53 @@ data class JcArgument(val index: Int, override val name: String, override val ty override fun accept(visitor: JcExprVisitor): T { return visitor.visitJcArgument(this) } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as JcArgument + + if (index != other.index) return false + if (type != other.type) return false + + return true + } + + override fun hashCode(): Int { + var result = index + result = 31 * result + type.hashCode() + return result + } } -data class JcLocalVar(override val name: String, override val type: JcType) : JcLocal { +/** + * @param name isn't considered in `equals` and `hashcode` + */ +data class JcLocalVar(val index: Int, override val name: String, override val type: JcType) : JcLocal { override fun toString(): String = name override fun accept(visitor: JcExprVisitor): T { return visitor.visitJcLocalVar(this) } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as JcLocalVar + + if (index != other.index) return false + if (type != other.type) return false + + return true + } + + override fun hashCode(): Int { + var result = index + result = 31 * result + type.hashCode() + return result + } } interface JcComplexValue : JcValue diff --git a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInst.kt b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInst.kt index 3491227ee..21fa62116 100644 --- a/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInst.kt +++ b/jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInst.kt @@ -808,6 +808,9 @@ data class JcRawThis(override val typeName: TypeName) : JcRawSimpleValue { } } +/** + * @param name isn't considered in `equals` and `hashcode` + */ data class JcRawArgument(val index: Int, override val name: String, override val typeName: TypeName) : JcRawLocal { companion object { @JvmStatic @@ -822,14 +825,53 @@ data class JcRawArgument(val index: Int, override val name: String, override val override fun accept(visitor: JcRawExprVisitor): T { return visitor.visitJcRawArgument(this) } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as JcRawArgument + + if (index != other.index) return false + if (typeName != other.typeName) return false + + return true + } + + override fun hashCode(): Int { + var result = index + result = 31 * result + typeName.hashCode() + return result + } } -data class JcRawLocalVar(override val name: String, override val typeName: TypeName) : JcRawLocal { +/** + * @param name isn't considered in `equals` and `hashcode` + */ +data class JcRawLocalVar(val index: Int, override val name: String, override val typeName: TypeName) : JcRawLocal { override fun toString(): String = name override fun accept(visitor: JcRawExprVisitor): T { return visitor.visitJcRawLocalVar(this) } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as JcRawLocalVar + + if (index != other.index) return false + if (typeName != other.typeName) return false + + return true + } + + override fun hashCode(): Int { + var result = index + result = 31 * result + typeName.hashCode() + return result + } } sealed interface JcRawComplexValue : JcRawValue diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/JcDatabaseImpl.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/JcDatabaseImpl.kt index 75de75e21..921bf9ddb 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/JcDatabaseImpl.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/JcDatabaseImpl.kt @@ -69,9 +69,9 @@ class JcDatabaseImpl( private fun List?.appendBuiltInFeatures(): List { if (this != null && any { it is ClasspathCache }) { - return this + listOf(KotlinMetadata, MethodInstructionsFeature) + return this + listOf(KotlinMetadata, MethodInstructionsFeature(settings.keepLocalVariableNames)) } - return listOf(ClasspathCache(settings.cacheSettings), KotlinMetadata, MethodInstructionsFeature) + orEmpty() + return listOf(ClasspathCache(settings.cacheSettings), KotlinMetadata, MethodInstructionsFeature(settings.keepLocalVariableNames)) + orEmpty() } override suspend fun classpath(dirOrJars: List, features: List?): JcClasspath { diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/JcSettings.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/JcSettings.kt index f1fd4e29e..c0d0142b4 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/JcSettings.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/JcSettings.kt @@ -45,6 +45,9 @@ class JcSettings { var persistentClearOnStart: Boolean? = null + var keepLocalVariableNames: Boolean = false + private set + /** jar files which should be loaded right after database is created */ var predefinedDirOrJars: List = persistentListOf() private set @@ -101,6 +104,10 @@ class JcSettings { predefinedDirOrJars = (predefinedDirOrJars + files).toPersistentList() } + fun keepLocalVariableNames() { + keepLocalVariableNames = true + } + /** * builder for watching file system changes * @param delay - delay between syncs diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/analysis/impl/StringConcatSimplifierTransformer.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/analysis/impl/StringConcatSimplifierTransformer.kt index 04430fe11..abfb2c433 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/analysis/impl/StringConcatSimplifierTransformer.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/analysis/impl/StringConcatSimplifierTransformer.kt @@ -27,7 +27,8 @@ import org.jacodb.impl.cfg.VirtualMethodRefImpl import org.jacodb.impl.cfg.methodRef import kotlin.collections.set -class StringConcatSimplifierTransformer(classpath: JcClasspath, private val list: JcInstList) : DefaultJcInstVisitor { +class StringConcatSimplifierTransformer(classpath: JcClasspath, private val list: JcInstList) : + DefaultJcInstVisitor { override val defaultInstHandler: (JcInst) -> JcInst get() = { it } @@ -39,6 +40,10 @@ class StringConcatSimplifierTransformer(classpath: JcClasspath, private val list private val stringType = classpath.findTypeOrNull() as JcClassType + private var localCounter = list + .flatMap { it.values.filterIsInstance() } + .maxOfOrNull { it.index }?.plus(1) ?: 0 + fun transform(): JcInstList { var changed = false for (inst in list) { @@ -101,7 +106,7 @@ class StringConcatSimplifierTransformer(classpath: JcClasspath, private val list it.name == "toString" && it.parameters.size == 1 && it.parameters.first().type == value.type } val toStringExpr = JcStaticCallExpr(method.methodRef(), listOf(value)) - val assignment = JcLocalVar("${value}String", stringType) + val assignment = JcLocalVar(localCounter++, "${value}String", stringType) instList += JcAssignInst(inst.location, assignment, toStringExpr) assignment } @@ -114,7 +119,7 @@ class StringConcatSimplifierTransformer(classpath: JcClasspath, private val list } val methodRef = VirtualMethodRefImpl.of(boxedType, method) val toStringExpr = JcVirtualCallExpr(methodRef, value, emptyList()) - val assignment = JcLocalVar("${value}String", stringType) + val assignment = JcLocalVar(localCounter++, "${value}String", stringType) instList += JcAssignInst(inst.location, assignment, toStringExpr) assignment } diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/bytecode/JcMethodImpl.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/bytecode/JcMethodImpl.kt index cd7a21975..10cadfd4f 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/bytecode/JcMethodImpl.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/bytecode/JcMethodImpl.kt @@ -70,7 +70,7 @@ class JcMethodImpl( internal fun parameterTypeAnnotationInfos(parameterIndex: Int): List = methodInfo.annotations.filter { it.typeRef != null && TypeReference(it.typeRef).sort == TypeReference.METHOD_FORMAL_PARAMETER - && TypeReference(it.typeRef).formalParameterIndex == parameterIndex + && TypeReference(it.typeRef).formalParameterIndex == parameterIndex } override val description get() = methodInfo.desc diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt index 0addf6376..41cfe704c 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt @@ -77,7 +77,7 @@ class JcInstListBuilder(val method: JcMethod,val instList: JcInstList inst.lhv.let { unprocessedLhv -> if (unprocessedLhv is JcRawLocalVar && unprocessedLhv.typeName == UNINIT_THIS) { convertedLocalVars.getOrPut(unprocessedLhv) { - JcRawLocalVar(unprocessedLhv.name, inst.rhv.typeName) + JcRawLocalVar(unprocessedLhv.index, unprocessedLhv.name, inst.rhv.typeName) } } else { unprocessedLhv @@ -338,8 +338,8 @@ class JcInstListBuilder(val method: JcMethod,val instList: JcInstList override fun visitJcRawLocalVar(value: JcRawLocalVar): JcExpr = convertedLocalVars[value]?.let { replacementForLocalVar -> - JcLocalVar(replacementForLocalVar.name, replacementForLocalVar.typeName.asType()) - } ?: JcLocalVar(value.name, value.typeName.asType()) + JcLocalVar(replacementForLocalVar.index, replacementForLocalVar.name, replacementForLocalVar.typeName.asType()) + } ?: JcLocalVar(value.index, value.name, value.typeName.asType()) override fun visitJcRawFieldRef(value: JcRawFieldRef): JcExpr { val type = value.declaringClass.asType() as JcClassType diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListImpl.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListImpl.kt index b121f04fd..8fe7a5485 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListImpl.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListImpl.kt @@ -46,7 +46,6 @@ open class JcInstListImpl( else -> " $it" } } - } class JcMutableInstListImpl(instructions: List) : JcInstListImpl(instructions), diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/MethodNodeBuilder.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/MethodNodeBuilder.kt index 595b5a424..d0c8137a5 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/MethodNodeBuilder.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/MethodNodeBuilder.kt @@ -17,6 +17,7 @@ package org.jacodb.impl.cfg import org.jacodb.api.JcMethod +import org.jacodb.api.JcParameter import org.jacodb.api.PredefinedPrimitives import org.jacodb.api.TypeName import org.jacodb.api.cfg.* @@ -26,6 +27,7 @@ import org.objectweb.asm.Opcodes import org.objectweb.asm.Opcodes.H_GETSTATIC import org.objectweb.asm.Type import org.objectweb.asm.tree.* +import java.util.ArrayList private val PredefinedPrimitives.smallIntegers get() = setOf(Boolean, Byte, Char, Short, Int) @@ -116,12 +118,26 @@ class MethodNodeBuilder( } private fun initializeFrame(method: JcMethod) { + var staticInc = 0 if (!method.isStatic) { val thisRef = JcRawThis(method.enclosingClass.name.typeName()) locals[thisRef] = localIndex++ + staticInc = 1 } + + val variables = method.asmNode().localVariables.orEmpty().sortedBy(LocalVariableNode::index) + + fun getName(parameter: JcParameter): String? { + val idx = parameter.index + staticInc + return if (idx < variables.size) { + variables[idx].name + } else { + parameter.name + } + } + for (parameter in method.parameters) { - val argument = JcRawArgument.of(parameter.index, parameter.name, parameter.type) + val argument = JcRawArgument.of(parameter.index, getName(parameter), parameter.type) locals[argument] = localIndex if (argument.typeName.isDWord) localIndex += 2 else localIndex++ 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 34f1559ca..e96c3398c 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 @@ -18,6 +18,7 @@ package org.jacodb.impl.cfg import kotlinx.collections.immutable.* import org.jacodb.api.JcMethod +import org.jacodb.api.JcParameter import org.jacodb.api.PredefinedPrimitives import org.jacodb.api.TypeName import org.jacodb.api.cfg.* @@ -27,6 +28,7 @@ import org.objectweb.asm.* import org.objectweb.asm.tree.* import java.util.* +const val LOCAL_VAR_START_CHARACTER = '%' private fun Int.toPrimitiveType(): TypeName = when (this) { Opcodes.T_CHAR -> PredefinedPrimitives.Char @@ -196,7 +198,8 @@ internal fun Map.toIdentityMap(): Map = toMap() class RawInstListBuilder( val method: JcMethod, - private val methodNode: MethodNode + private val methodNode: MethodNode, + private val keepLocalVariableNames: Boolean, ) { private val frames = identityMap() private val labels = identityMap() @@ -410,8 +413,10 @@ class RawInstListBuilder( override: Boolean = false ): JcRawAssignInst { val oldVar = currentFrame.locals[variable]?.let { - val infoFromLocalVars = methodNode.localVariables.find { it.index == variable && insn.isBetween(it.start, it.end) } - val isArg = variable < argCounter && infoFromLocalVars != null && infoFromLocalVars.start == methodNode.instructions.firstOrNull { it is LabelNode } + val infoFromLocalVars = + methodNode.localVariables.find { it.index == variable && insn.isBetween(it.start, it.end) } + val isArg = + variable < argCounter && infoFromLocalVars != null && infoFromLocalVars.start == methodNode.instructions.firstOrNull { it is LabelNode } if (expr.typeName.isPrimitive.xor(it.typeName.isPrimitive) && it.typeName.typeName != PredefinedPrimitives.Null && !isArg @@ -475,7 +480,7 @@ class RawInstListBuilder( } private fun nextRegister(typeName: TypeName): JcRawValue { - return JcRawLocalVar("%${localCounter++}", typeName) + return JcRawLocalVar(localCounter, "$LOCAL_VAR_START_CHARACTER${localCounter++}", typeName) } private fun nextRegisterDeclaredVariable(typeName: TypeName, variable: Int, insn: AbstractInsnNode): JcRawValue { @@ -483,15 +488,17 @@ class RawInstListBuilder( .filterIsInstance() .firstOrNull() - val declaredTypeName = methodNode.localVariables + val lvNode = methodNode.localVariables .singleOrNull { it.index == variable && it.start == nextLabel } - ?.desc - ?.typeName() + + val declaredTypeName = lvNode?.desc?.typeName() + val idx = localCounter++ + val lvName = lvNode?.name?.takeIf { keepLocalVariableNames } ?: "$LOCAL_VAR_START_CHARACTER$idx" return if (declaredTypeName != null && !declaredTypeName.isPrimitive && !typeName.isArray) { - JcRawLocalVar("%${localCounter++}", declaredTypeName) + JcRawLocalVar(idx, lvName, declaredTypeName) } else { - JcRawLocalVar("%${localCounter++}", typeName) + JcRawLocalVar(idx, lvName, typeName) } } @@ -538,11 +545,24 @@ class RawInstListBuilder( private fun createInitialFrame(): Frame { val locals = hashMapOf() argCounter = 0 + var staticInc = 0 if (!method.isStatic) { locals[argCounter++] = thisRef() + staticInc = 1 + } + val variables = methodNode.localVariables.orEmpty().sortedBy(LocalVariableNode::index) + + fun getName(parameter: JcParameter): String? { + val idx = parameter.index + staticInc + return if (idx < variables.size) { + variables[idx].name + } else { + parameter.name + } } + for (parameter in method.parameters) { - val argument = JcRawArgument.of(parameter.index, parameter.name, parameter.type) + val argument = JcRawArgument.of(parameter.index, getName(parameter), parameter.type) locals[argCounter] = argument if (argument.typeName.isDWord) argCounter += 2 else argCounter++ @@ -909,15 +929,17 @@ class RawInstListBuilder( }.toMap() else -> frame.locals.filterKeys { it in this }.mapValues { + val value = it.value when { - it.value is JcRawLocalVar && it.value.typeName != this[it.key]!! && this[it.key] !in blackListForTypeRefinement -> JcRawLocalVar( - (it.value as JcRawLocalVar).name, - this[it.key]!! - ).also { newLocal -> - localTypeRefinement[it.value as JcRawLocalVar] = newLocal - } - - else -> it.value + value is JcRawLocalVar && value.typeName != this[it.key]!! && this[it.key] !in blackListForTypeRefinement -> + JcRawLocalVar( + value.index, + value.name, + this[it.key]!! + ).also { newLocal -> + localTypeRefinement[value] = newLocal + } + else -> value } } } @@ -996,15 +1018,17 @@ class RawInstListBuilder( } else -> frame.stack.withIndex().filter { it.index in this }.map { + val value = it.value when { - it.value is JcRawLocalVar && it.value.typeName != this[it.index]!! && this[it.index] !in blackListForTypeRefinement -> JcRawLocalVar( - (it.value as JcRawLocalVar).name, - this[it.index]!! - ).also { newLocal -> - localTypeRefinement[it.value as JcRawLocalVar] = newLocal - } - - else -> it.value + value is JcRawLocalVar && value.typeName != this[it.index]!! && this[it.index] !in blackListForTypeRefinement -> + JcRawLocalVar( + value.index, + value.name, + this[it.index]!! + ).also { newLocal -> + localTypeRefinement[value] = newLocal + } + else -> value } } } @@ -1109,7 +1133,10 @@ class RawInstListBuilder( nextInst != null && nextInst.isBranchingInst -> local nextInst != null && nextInst is VarInsnNode && nextInst.`var` == variable -> local //Workaround for if (x++) if x is function argument - prevInst != null && local is JcRawArgument && prevInst is VarInsnNode && prevInst.`var` == variable -> nextRegister(local.typeName) + prevInst != null && local is JcRawArgument && prevInst is VarInsnNode && prevInst.`var` == variable -> nextRegister( + local.typeName + ) + local is JcRawArgument -> local else -> nextRegister(local.typeName) } 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 337e472a6..40ddbe509 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 @@ -29,7 +29,6 @@ import org.jacodb.impl.cfg.util.InstructionFilter * the frames merging) */ internal class Simplifier { - fun simplify(jcClasspath: JcClasspath, instList: JcInstListImpl): JcInstListImpl { // clear the assignments that are repeated inside single basic block var instructionList = cleanRepeatedAssignments(instList) @@ -42,9 +41,9 @@ internal class Simplifier { val oldSize = instructionList.instructions.size instructionList = instructionList.filterNot(InstructionFilter { it is JcRawAssignInst - && it.lhv is JcRawSimpleValue - && it.rhv is JcRawValue - && uses.getOrDefault(it.lhv, 0) == 0 + && it.lhv is JcRawSimpleValue + && it.rhv is JcRawValue + && uses.getOrDefault(it.lhv, 0) == 0 }) } while (instructionList.instructions.size != oldSize) @@ -53,11 +52,33 @@ internal class Simplifier { // (e.g. `a = b` and `b = a`) and not used anywhere else; also need to run several times // because of potential dependencies between such variables val assignmentsMap = computeAssignments(instructionList) - val replacements = assignmentsMap - .filter { (assignmentsMap[it.value.first()]?.let { it.size == 1 } ?: true) } - .filterValues { it.first() is JcRawLocalVar && it.drop(1).all { it !is JcRawLocalVar } } - .map { it.key to it.value.first() } - .toMap() + val replacements = buildMap { + for ((to, froms) in assignmentsMap) { + if (froms.drop(1).any { it is JcRawLocalVar }) { + continue + } + val firstFrom = (froms.first() as? JcRawLocalVar) ?: continue + val fromAssignments = assignmentsMap[firstFrom] + if (fromAssignments != null && fromAssignments.size != 1) { + continue + } + put(to, firstFrom) + } + } + + val extendedReplacements = buildMap { + for ((to, from) in replacements) { + if (!to.name.startsWith(LOCAL_VAR_START_CHARACTER)) { + val actual = to.copy(typeName = from.typeName) + // to keep original names ---------^ + put(to, actual) + put(from, actual) + } else { + put(to, from) + } + } + } + instructionList = instructionList .filterNot(InstructionFilter { if (it !is JcRawAssignInst) return@InstructionFilter false @@ -65,7 +86,7 @@ internal class Simplifier { val rhv = it.rhv as? JcRawSimpleValue ?: return@InstructionFilter false replacements[lhv] == rhv && replacements[rhv] == lhv }) - .map(ExprMapper(replacements.toMap())) + .map(ExprMapper(extendedReplacements)) .filterNot(InstructionFilter { it is JcRawAssignInst && it.rhv == it.lhv }) @@ -178,19 +199,22 @@ internal class Simplifier { for (inst in instList) { if (inst is JcRawAssignInst) { + val lhv = inst.lhv val rhv = inst.rhv - if (inst.lhv is JcRawSimpleValue + if (lhv is JcRawSimpleValue && rhv is JcRawLocalVar - && uses.getOrDefault(inst.rhv, emptySet()).let { it.size == 1 && it.firstOrNull() == inst } + && uses.getOrDefault(rhv, emptySet()).let { it.size == 1 && it.firstOrNull() == inst } && rhv !in reservedValues ) { - val lhv = inst.lhv val lhvUsage = uses.getOrDefault(lhv, emptySet()).firstOrNull() val assignInstructionToReplacement = instList.firstOrNull { it is JcRawAssignInst && it.lhv == lhv } + val assignInstructionToRhv = instList.firstOrNull { it is JcRawAssignInst && it.lhv == rhv} val didNotAssignedBefore = lhvUsage == null || - assignInstructionToReplacement == null || - !instList.isBefore(assignInstructionToReplacement, lhvUsage) + assignInstructionToReplacement == null || + !instList.isBefore(assignInstructionToReplacement, lhvUsage) || + assignInstructionToRhv == null || + instList.areSequential(assignInstructionToRhv, inst) if (lhvUsage == null || !instList.isBefore(lhvUsage, inst)) { if (didNotAssignedBefore) { replacements[rhv] = lhv @@ -209,8 +233,12 @@ internal class Simplifier { return indexOf(one) < indexOf(another) } - private fun computeAssignments(instList: JcInstListImpl): Map> { - val assignments = mutableMapOf>() + private fun JcInstListImpl.areSequential(one: JcRawInst, another: JcRawInst): Boolean { + return indexOf(one) + 1 == indexOf(another) + } + + private fun computeAssignments(instList: JcInstListImpl): Map> { + val assignments = mutableMapOf>() for (inst in instList) { if (inst is JcRawAssignInst) { val lhv = inst.lhv @@ -237,7 +265,7 @@ internal class Simplifier { } val replacement = types.filterValues { it.size > 1 } .mapValues { - JcRawLocalVar(it.key.name, it.key.typeName) + JcRawLocalVar(it.key.index, it.key.name, it.key.typeName) } return instList.map(ExprMapper(replacement.toMap())) } diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/features/classpaths/MethodInstructionsFeature.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/features/classpaths/MethodInstructionsFeature.kt index f7e6c778b..02e18cfab 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/features/classpaths/MethodInstructionsFeature.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/features/classpaths/MethodInstructionsFeature.kt @@ -32,7 +32,9 @@ import org.jacodb.impl.features.classpaths.AbstractJcInstResult.JcFlowGraphResul import org.jacodb.impl.features.classpaths.AbstractJcInstResult.JcInstListResultImpl import org.jacodb.impl.features.classpaths.AbstractJcInstResult.JcRawInstListResultImpl -object MethodInstructionsFeature : JcMethodExtFeature { +class MethodInstructionsFeature( + private val keepLocalVariableNames: Boolean +) : JcMethodExtFeature { private val JcMethod.methodFeatures get() = enclosingClass.classpath.features?.filterIsInstance().orEmpty() @@ -50,7 +52,7 @@ object MethodInstructionsFeature : JcMethodExtFeature { } override fun rawInstList(method: JcMethod): JcMethodExtFeature.JcRawInstListResult { - val list: JcInstList = RawInstListBuilder(method, method.asmNode()).build() + val list: JcInstList = RawInstListBuilder(method, method.asmNode(), keepLocalVariableNames).build() return JcRawInstListResultImpl(method, method.methodFeatures.fold(list) { value, feature -> feature.transformRawInstList(method, value) }) diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/features/classpaths/virtual/JcVirtualMethod.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/features/classpaths/virtual/JcVirtualMethod.kt index 5e35bc22e..811b1fc27 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/features/classpaths/virtual/JcVirtualMethod.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/features/classpaths/virtual/JcVirtualMethod.kt @@ -27,8 +27,8 @@ import org.jacodb.api.cfg.JcInst import org.jacodb.api.cfg.JcInstList import org.jacodb.api.cfg.JcRawInst import org.jacodb.impl.bytecode.JcDeclarationImpl +import org.jacodb.impl.cfg.JcGraphImpl import org.jacodb.impl.cfg.JcInstListImpl -import org.jacodb.impl.features.classpaths.MethodInstructionsFeature import org.objectweb.asm.Opcodes import org.objectweb.asm.tree.MethodNode @@ -43,9 +43,7 @@ interface JcVirtualMethod : JcMethod { override val instList: JcInstList get() = JcInstListImpl(emptyList()) - override fun flowGraph(): JcGraph { - return MethodInstructionsFeature.flowGraph(this).flowGraph - } + override fun flowGraph(): JcGraph = JcGraphImpl(this, instList.instructions) } open class JcVirtualParameter( 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 724bea248..2fecada52 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 @@ -61,10 +61,22 @@ class InstructionsTest : BaseInstructionsTest() { val instructions = method.instList.instructions val firstUse = instructions.indexOfFirst { it.callExpr?.method?.method == use } val assign = instructions[firstUse + 1] as JcAssignInst - assertEquals("%4", (assign.lhv as JcLocalVar).name) - assertEquals("%1", (assign.rhv as JcLocalVar).name) + assertEquals("b", (assign.lhv as JcLocalVar).name) + assertEquals("a", (assign.rhv as JcLocalVar).name) } + @Test + fun `invoke inst`() { + val clazz = cp.findClass() + val method = clazz.declaredMethods.first { it.name == "invoke" } + val instructions = method.instList.instructions + val usedArgumentExprs = instructions.filter { it.callExpr?.method?.method?.name == "println" } + .flatMap { it.callExpr?.args.orEmpty() } + val usedArgumentNames = usedArgumentExprs.map { (it as JcArgument).name } + assertEquals(listOf("i", "j", "b", "d"), usedArgumentNames) + } + + @Test fun `cmp insts`() { val clazz = cp.findClass() diff --git a/jacodb-core/src/testFixtures/java/org/jacodb/testing/cfg/SimpleAlias1.java b/jacodb-core/src/testFixtures/java/org/jacodb/testing/cfg/SimpleAlias1.java index 9df10aaf1..7a5446c98 100644 --- a/jacodb-core/src/testFixtures/java/org/jacodb/testing/cfg/SimpleAlias1.java +++ b/jacodb-core/src/testFixtures/java/org/jacodb/testing/cfg/SimpleAlias1.java @@ -39,6 +39,13 @@ public static void main(String[] args) { Benchmark.test("b", "{allocId:1, mayAlias:[a,b], notMayAlias:[], mustAlias:[a,b], notMustAlias:[]}"); } + + public void invoke(int i, long j, byte b, double d) { + System.out.println(i); + System.out.println(j); + System.out.println(b); + System.out.println(d); + } } class A { diff --git a/jacodb-core/src/testFixtures/kotlin/org/jacodb/testing/BaseTest.kt b/jacodb-core/src/testFixtures/kotlin/org/jacodb/testing/BaseTest.kt index d3267cb4d..5e91288d9 100644 --- a/jacodb-core/src/testFixtures/kotlin/org/jacodb/testing/BaseTest.kt +++ b/jacodb-core/src/testFixtures/kotlin/org/jacodb/testing/BaseTest.kt @@ -85,6 +85,7 @@ open class WithDB(vararg features: Any) : JcDatabaseHolder { // persistent("D:\\work\\jacodb\\jcdb-index.db") loadByteCode(allClasspath) useProcessJavaRuntime() + keepLocalVariableNames() installFeatures(*dbFeatures.toTypedArray()) }.also { it.awaitBackgroundJobs() @@ -134,6 +135,7 @@ open class WithRestoredDB(vararg features: JcFeature<*, *>) : WithDB(*features) persistent(jdbcLocation) loadByteCode(allClasspath) useProcessJavaRuntime() + keepLocalVariableNames() installFeatures(*dbFeatures.toTypedArray()) }.also { it.awaitBackgroundJobs()