Skip to content

Commit 389ea0f

Browse files
Keep local variable names feature (#215)
1 parent 1a0fb7e commit 389ea0f

File tree

18 files changed

+263
-75
lines changed

18 files changed

+263
-75
lines changed

jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/BaseAnalysisTest.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,9 @@ abstract class BaseAnalysisTest : BaseTest() {
8888
// TODO: think about better assertions here
8989
assertEquals(expectedLocations.size, sinks.size)
9090
expectedLocations.forEach { expected ->
91-
assertTrue(sinks.any { it.contains(expected) })
91+
assertTrue(sinks.any { it.contains(expected) }) {
92+
"$expected unmatched in:\n${sinks.joinToString("\n")}"
93+
}
9294
}
9395
}
9496

jacodb-analysis/src/test/kotlin/org/jacodb/analysis/impl/NpeAnalysisTest.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class NpeAnalysisTest : BaseAnalysisTest() {
6060

6161
@Test
6262
fun `analyze simple NPE`() {
63-
testOneMethod<NpeExamples>("npeOnLength", listOf("%3 = %0.length()"))
63+
testOneMethod<NpeExamples>("npeOnLength", listOf("%3 = x.length()"))
6464
}
6565

6666
@Test
@@ -72,7 +72,7 @@ class NpeAnalysisTest : BaseAnalysisTest() {
7272
fun `analyze NPE after fun with two exits`() {
7373
testOneMethod<NpeExamples>(
7474
"npeAfterTwoExits",
75-
listOf("%4 = %0.length()", "%5 = %1.length()")
75+
listOf("%4 = x.length()", "%5 = y.length()")
7676
)
7777
}
7878

@@ -85,15 +85,15 @@ class NpeAnalysisTest : BaseAnalysisTest() {
8585
fun `consecutive NPEs handled properly`() {
8686
testOneMethod<NpeExamples>(
8787
"consecutiveNPEs",
88-
listOf("%2 = arg$0.length()", "%4 = arg$0.length()")
88+
listOf("a = x.length()", "c = x.length()")
8989
)
9090
}
9191

9292
@Test
9393
fun `npe on virtual call when possible`() {
9494
testOneMethod<NpeExamples>(
9595
"possibleNPEOnVirtualCall",
96-
listOf("%0 = arg\$0.length()")
96+
listOf("%0 = x.length()")
9797
)
9898
}
9999

@@ -107,7 +107,7 @@ class NpeAnalysisTest : BaseAnalysisTest() {
107107

108108
@Test
109109
fun `basic test for NPE on fields`() {
110-
testOneMethod<NpeExamples>("simpleNPEOnField", listOf("%8 = %6.length()"))
110+
testOneMethod<NpeExamples>("simpleNPEOnField", listOf("len2 = second.length()"))
111111
}
112112

113113
@Disabled("Flowdroid architecture not supported for async ifds yet")
@@ -152,7 +152,7 @@ class NpeAnalysisTest : BaseAnalysisTest() {
152152

153153
@Test
154154
fun `NPE on uninitialized array element dereferencing`() {
155-
testOneMethod<NpeExamples>("simpleArrayNPE", listOf("%5 = %4.length()"))
155+
testOneMethod<NpeExamples>("simpleArrayNPE", listOf("b = %4.length()"))
156156
}
157157

158158
@Test
@@ -174,7 +174,7 @@ class NpeAnalysisTest : BaseAnalysisTest() {
174174

175175
@Test
176176
fun `dereferencing field of null object`() {
177-
testOneMethod<NpeExamples>("npeOnFieldDeref", listOf("%1 = %0.field"))
177+
testOneMethod<NpeExamples>("npeOnFieldDeref", listOf("s = a.field"))
178178
}
179179

180180
@Test

jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcInst.kt

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -811,8 +811,10 @@ interface JcLocal : JcSimpleValue {
811811
val name: String
812812
}
813813

814+
/**
815+
* @param name isn't considered in `equals` and `hashcode`
816+
*/
814817
data class JcArgument(val index: Int, override val name: String, override val type: JcType) : JcLocal {
815-
816818
companion object {
817819
@JvmStatic
818820
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
825827
override fun <T> accept(visitor: JcExprVisitor<T>): T {
826828
return visitor.visitJcArgument(this)
827829
}
830+
831+
override fun equals(other: Any?): Boolean {
832+
if (this === other) return true
833+
if (javaClass != other?.javaClass) return false
834+
835+
other as JcArgument
836+
837+
if (index != other.index) return false
838+
if (type != other.type) return false
839+
840+
return true
841+
}
842+
843+
override fun hashCode(): Int {
844+
var result = index
845+
result = 31 * result + type.hashCode()
846+
return result
847+
}
828848
}
829849

830-
data class JcLocalVar(override val name: String, override val type: JcType) : JcLocal {
850+
/**
851+
* @param name isn't considered in `equals` and `hashcode`
852+
*/
853+
data class JcLocalVar(val index: Int, override val name: String, override val type: JcType) : JcLocal {
831854
override fun toString(): String = name
832855

833856
override fun <T> accept(visitor: JcExprVisitor<T>): T {
834857
return visitor.visitJcLocalVar(this)
835858
}
859+
860+
override fun equals(other: Any?): Boolean {
861+
if (this === other) return true
862+
if (javaClass != other?.javaClass) return false
863+
864+
other as JcLocalVar
865+
866+
if (index != other.index) return false
867+
if (type != other.type) return false
868+
869+
return true
870+
}
871+
872+
override fun hashCode(): Int {
873+
var result = index
874+
result = 31 * result + type.hashCode()
875+
return result
876+
}
836877
}
837878

838879
interface JcComplexValue : JcValue

jacodb-api/src/main/kotlin/org/jacodb/api/cfg/JcRawInst.kt

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,9 @@ data class JcRawThis(override val typeName: TypeName) : JcRawSimpleValue {
808808
}
809809
}
810810

811+
/**
812+
* @param name isn't considered in `equals` and `hashcode`
813+
*/
811814
data class JcRawArgument(val index: Int, override val name: String, override val typeName: TypeName) : JcRawLocal {
812815
companion object {
813816
@JvmStatic
@@ -822,14 +825,53 @@ data class JcRawArgument(val index: Int, override val name: String, override val
822825
override fun <T> accept(visitor: JcRawExprVisitor<T>): T {
823826
return visitor.visitJcRawArgument(this)
824827
}
828+
829+
override fun equals(other: Any?): Boolean {
830+
if (this === other) return true
831+
if (javaClass != other?.javaClass) return false
832+
833+
other as JcRawArgument
834+
835+
if (index != other.index) return false
836+
if (typeName != other.typeName) return false
837+
838+
return true
839+
}
840+
841+
override fun hashCode(): Int {
842+
var result = index
843+
result = 31 * result + typeName.hashCode()
844+
return result
845+
}
825846
}
826847

827-
data class JcRawLocalVar(override val name: String, override val typeName: TypeName) : JcRawLocal {
848+
/**
849+
* @param name isn't considered in `equals` and `hashcode`
850+
*/
851+
data class JcRawLocalVar(val index: Int, override val name: String, override val typeName: TypeName) : JcRawLocal {
828852
override fun toString(): String = name
829853

830854
override fun <T> accept(visitor: JcRawExprVisitor<T>): T {
831855
return visitor.visitJcRawLocalVar(this)
832856
}
857+
858+
override fun equals(other: Any?): Boolean {
859+
if (this === other) return true
860+
if (javaClass != other?.javaClass) return false
861+
862+
other as JcRawLocalVar
863+
864+
if (index != other.index) return false
865+
if (typeName != other.typeName) return false
866+
867+
return true
868+
}
869+
870+
override fun hashCode(): Int {
871+
var result = index
872+
result = 31 * result + typeName.hashCode()
873+
return result
874+
}
833875
}
834876

835877
sealed interface JcRawComplexValue : JcRawValue

jacodb-core/src/main/kotlin/org/jacodb/impl/JcDatabaseImpl.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@ class JcDatabaseImpl(
6969

7070
private fun List<JcClasspathFeature>?.appendBuiltInFeatures(): List<JcClasspathFeature> {
7171
if (this != null && any { it is ClasspathCache }) {
72-
return this + listOf(KotlinMetadata, MethodInstructionsFeature)
72+
return this + listOf(KotlinMetadata, MethodInstructionsFeature(settings.keepLocalVariableNames))
7373
}
74-
return listOf(ClasspathCache(settings.cacheSettings), KotlinMetadata, MethodInstructionsFeature) + orEmpty()
74+
return listOf(ClasspathCache(settings.cacheSettings), KotlinMetadata, MethodInstructionsFeature(settings.keepLocalVariableNames)) + orEmpty()
7575
}
7676

7777
override suspend fun classpath(dirOrJars: List<File>, features: List<JcClasspathFeature>?): JcClasspath {

jacodb-core/src/main/kotlin/org/jacodb/impl/JcSettings.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ class JcSettings {
4545

4646
var persistentClearOnStart: Boolean? = null
4747

48+
var keepLocalVariableNames: Boolean = false
49+
private set
50+
4851
/** jar files which should be loaded right after database is created */
4952
var predefinedDirOrJars: List<File> = persistentListOf()
5053
private set
@@ -101,6 +104,10 @@ class JcSettings {
101104
predefinedDirOrJars = (predefinedDirOrJars + files).toPersistentList()
102105
}
103106

107+
fun keepLocalVariableNames() {
108+
keepLocalVariableNames = true
109+
}
110+
104111
/**
105112
* builder for watching file system changes
106113
* @param delay - delay between syncs

jacodb-core/src/main/kotlin/org/jacodb/impl/analysis/impl/StringConcatSimplifierTransformer.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ import org.jacodb.impl.cfg.VirtualMethodRefImpl
2727
import org.jacodb.impl.cfg.methodRef
2828
import kotlin.collections.set
2929

30-
class StringConcatSimplifierTransformer(classpath: JcClasspath, private val list: JcInstList<JcInst>) : DefaultJcInstVisitor<JcInst> {
30+
class StringConcatSimplifierTransformer(classpath: JcClasspath, private val list: JcInstList<JcInst>) :
31+
DefaultJcInstVisitor<JcInst> {
3132

3233
override val defaultInstHandler: (JcInst) -> JcInst
3334
get() = { it }
@@ -39,6 +40,10 @@ class StringConcatSimplifierTransformer(classpath: JcClasspath, private val list
3940

4041
private val stringType = classpath.findTypeOrNull<String>() as JcClassType
4142

43+
private var localCounter = list
44+
.flatMap { it.values.filterIsInstance<JcLocalVar>() }
45+
.maxOfOrNull { it.index }?.plus(1) ?: 0
46+
4247
fun transform(): JcInstList<JcInst> {
4348
var changed = false
4449
for (inst in list) {
@@ -101,7 +106,7 @@ class StringConcatSimplifierTransformer(classpath: JcClasspath, private val list
101106
it.name == "toString" && it.parameters.size == 1 && it.parameters.first().type == value.type
102107
}
103108
val toStringExpr = JcStaticCallExpr(method.methodRef(), listOf(value))
104-
val assignment = JcLocalVar("${value}String", stringType)
109+
val assignment = JcLocalVar(localCounter++, "${value}String", stringType)
105110
instList += JcAssignInst(inst.location, assignment, toStringExpr)
106111
assignment
107112
}
@@ -114,7 +119,7 @@ class StringConcatSimplifierTransformer(classpath: JcClasspath, private val list
114119
}
115120
val methodRef = VirtualMethodRefImpl.of(boxedType, method)
116121
val toStringExpr = JcVirtualCallExpr(methodRef, value, emptyList())
117-
val assignment = JcLocalVar("${value}String", stringType)
122+
val assignment = JcLocalVar(localCounter++, "${value}String", stringType)
118123
instList += JcAssignInst(inst.location, assignment, toStringExpr)
119124
assignment
120125
}

jacodb-core/src/main/kotlin/org/jacodb/impl/bytecode/JcMethodImpl.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ class JcMethodImpl(
7070
internal fun parameterTypeAnnotationInfos(parameterIndex: Int): List<AnnotationInfo> =
7171
methodInfo.annotations.filter {
7272
it.typeRef != null && TypeReference(it.typeRef).sort == TypeReference.METHOD_FORMAL_PARAMETER
73-
&& TypeReference(it.typeRef).formalParameterIndex == parameterIndex
73+
&& TypeReference(it.typeRef).formalParameterIndex == parameterIndex
7474
}
7575

7676
override val description get() = methodInfo.desc

jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListBuilder.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class JcInstListBuilder(val method: JcMethod,val instList: JcInstList<JcRawInst>
7777
inst.lhv.let { unprocessedLhv ->
7878
if (unprocessedLhv is JcRawLocalVar && unprocessedLhv.typeName == UNINIT_THIS) {
7979
convertedLocalVars.getOrPut(unprocessedLhv) {
80-
JcRawLocalVar(unprocessedLhv.name, inst.rhv.typeName)
80+
JcRawLocalVar(unprocessedLhv.index, unprocessedLhv.name, inst.rhv.typeName)
8181
}
8282
} else {
8383
unprocessedLhv
@@ -338,8 +338,8 @@ class JcInstListBuilder(val method: JcMethod,val instList: JcInstList<JcRawInst>
338338

339339
override fun visitJcRawLocalVar(value: JcRawLocalVar): JcExpr =
340340
convertedLocalVars[value]?.let { replacementForLocalVar ->
341-
JcLocalVar(replacementForLocalVar.name, replacementForLocalVar.typeName.asType())
342-
} ?: JcLocalVar(value.name, value.typeName.asType())
341+
JcLocalVar(replacementForLocalVar.index, replacementForLocalVar.name, replacementForLocalVar.typeName.asType())
342+
} ?: JcLocalVar(value.index, value.name, value.typeName.asType())
343343

344344
override fun visitJcRawFieldRef(value: JcRawFieldRef): JcExpr {
345345
val type = value.declaringClass.asType() as JcClassType

jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/JcInstListImpl.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ open class JcInstListImpl<INST>(
4646
else -> " $it"
4747
}
4848
}
49-
5049
}
5150

5251
class JcMutableInstListImpl<INST>(instructions: List<INST>) : JcInstListImpl<INST>(instructions),

0 commit comments

Comments
 (0)