Skip to content

Commit

Permalink
JacoDB produces incorrect 3-address code for IINC instruction (#158)
Browse files Browse the repository at this point in the history
JacoDB produces incorrect 3-address code for IINC instruction #146

Co-authored-by: Sergey Pospelov <[email protected]>
  • Loading branch information
lehvolk and sergeypospelov authored Aug 31, 2023
1 parent c0616ca commit 07d3ffc
Show file tree
Hide file tree
Showing 7 changed files with 320 additions and 252 deletions.
271 changes: 101 additions & 170 deletions jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/RawInstListBuilder.kt

Large diffs are not rendered by default.

13 changes: 1 addition & 12 deletions jacodb-core/src/main/kotlin/org/jacodb/impl/cfg/Simplifier.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright 2022 UnitTestBot contributors (utbot.org)
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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
}
}
77 changes: 10 additions & 67 deletions jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/IRTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,35 +16,33 @@

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
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
Expand Down Expand Up @@ -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<IRExamples>())
Expand All @@ -285,7 +279,7 @@ class IRTest : BaseTest() {
}


@Test
@Test
fun `get ir of self`() {
testClass(cp.findClass<JcClasspathImpl>())
testClass(cp.findClass<JcClassOrInterfaceImpl>())
Expand Down Expand Up @@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -51,7 +50,7 @@ import java.util.concurrent.ConcurrentHashMap
import javax.activation.DataHandler


class InstructionsTest : BaseTest() {
class InstructionsTest : BaseInstructionsTest() {

companion object : WithDB()

Expand Down Expand Up @@ -267,6 +266,48 @@ class InstructionsTest : BaseTest() {
}
assertEquals("defaultMethod", callDefaultMethod.method.method.name)
}


@Test
fun `iinc should work`() {
val clazz = cp.findClass<Incrementation>()

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<Incrementation>()

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<Incrementation>()

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<Incrementation>()

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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2022 UnitTestBot contributors (utbot.org)
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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;
}

}

0 comments on commit 07d3ffc

Please sign in to comment.