Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
fix nd without loading class
  • Loading branch information
SBOne-Kenobi committed Jun 1, 2023
commit e741e96b60bfe74ab84bf13569c54679128574b0
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,7 @@ open class ClassId @JvmOverloads constructor(
*/
open val allMethods: Sequence<MethodId>
get() = generateSequence(jClass) { it.superclass }
.flatMap { it.interfaces.toMutableList() + it }
.mapNotNull { it.declaredMethods }
.flatMap { it.toList() }
.map { it.executableId }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,57 +3,77 @@ package org.utbot.instrumentation.instrumentation.execution.ndd
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.MethodId
import org.utbot.framework.plugin.api.util.*

class NonDeterministicBytecodeInserter {
private val internalName = Type.getInternalName(NonDeterministicResultStorage::class.java)

private fun ClassId.descriptor(): String = when (this) {
booleanClassId -> "Z"
byteClassId -> "B"
charClassId -> "C"
shortClassId -> "S"
intClassId -> "I"
longClassId -> "J"
floatClassId -> "F"
doubleClassId -> "D"
else -> "Ljava/lang/Object;"
private fun String.getUnifiedParamsTypes(): List<String> {
val list = mutableListOf<String>()
var readObject = false
for (c in this) {
if (c == '(') {
continue
}
if (c == ')') {
break
}
if (readObject) {
if (c == ';') {
readObject = false
list.add("Ljava/lang/Object;")
}
} else if (c == 'L') {
readObject = true
} else {
list.add(c.toString())
}
}

return list
}

private fun MethodId.toStoreDescriptor(): String = buildString {
private fun String.unifyTypeDescriptor(): String =
if (startsWith('L')) {
"Ljava/lang/Object;"
} else {
this
}

private fun String.getReturnType(): String =
substringAfter(')')

private fun getStoreDescriptor(descriptor: String): String = buildString {
append('(')
append(returnType.descriptor())
append(descriptor.getReturnType().unifyTypeDescriptor())
append("Ljava/lang/String;)V")
}

private fun MethodVisitor.invoke(name: String, descriptor: String) {
visitMethodInsn(Opcodes.INVOKESTATIC, internalName, name, descriptor, false)
}

fun insertAfterNDMethod(mv: MethodVisitor, methodId: MethodId) {
fun insertAfterNDMethod(mv: MethodVisitor, owner: String, name: String, descriptor: String, isStatic: Boolean) {
mv.visitInsn(Opcodes.DUP)
mv.visitLdcInsn(NonDeterministicResultStorage.methodToSignature(methodId))
mv.invoke(if (methodId.isStatic) "storeStatic" else "storeCall", methodId.toStoreDescriptor())
mv.visitLdcInsn(NonDeterministicResultStorage.makeSignature(owner, name, descriptor))
mv.invoke(if (isStatic) "storeStatic" else "storeCall", getStoreDescriptor(descriptor))
}

fun insertBeforeNDMethod(mv: MethodVisitor, methodId: MethodId) {
if (methodId.isStatic) {
fun insertBeforeNDMethod(mv: MethodVisitor, descriptor: String, isStatic: Boolean) {
if (isStatic) {
return
}

methodId.parameters.asReversed().forEach {
val desc = it.descriptor()
mv.invoke("putParameter${desc[0]}", "($desc)V")
val params = descriptor.getUnifiedParamsTypes()

params.asReversed().forEach {
mv.invoke("putParameter${it[0]}", "($it)V")
}

mv.visitInsn(Opcodes.DUP)
mv.invoke("saveInstance", "(Ljava/lang/Object;)V")

methodId.parameters.forEach {
val desc = it.descriptor()
mv.invoke("peakParameter${desc[0]}", "()$desc")
params.forEach {
mv.invoke("peakParameter${it[0]}", "()$it")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,20 @@ package org.utbot.instrumentation.instrumentation.execution.ndd

import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.commons.Method
import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.MethodId
import org.utbot.framework.plugin.api.util.executableId
import org.utbot.framework.plugin.api.util.id
import org.utbot.framework.plugin.api.util.utContext
import org.objectweb.asm.Opcodes
import org.utbot.instrumentation.Settings

class NonDeterministicClassVisitor(
classVisitor: ClassVisitor,
private val detector: NonDeterministicDetector
) : ClassVisitor(Settings.ASM_API, classVisitor) {

private var currentClass: String? = null

private fun getOwnerClass(owner: String): Class<*> =
utContext.classLoader.loadClass(owner.replace('/', '.'))

private fun getMethodAndOwnerId(owner: String?, name: String?, descriptor: String?): Pair<ClassId, MethodId>? {
if (owner == null || name == null || descriptor == null) {
return null
}
val clazz = getOwnerClass(owner)
val method = clazz.methods.find {
it.name == name && Method.getMethod(it).descriptor == descriptor
} ?: return null
return clazz.id to method.executableId
}
private lateinit var currentClass: String

override fun visit(
version: Int,
access: Int,
name: String?,
name: String,
signature: String?,
superName: String?,
interfaces: Array<out String>?
Expand All @@ -45,40 +26,42 @@ class NonDeterministicClassVisitor(

override fun visitMethod(
access: Int,
name: String?,
descriptor: String?,
name: String,
descriptor: String,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor {
val mv = cv.visitMethod(access, name, descriptor, signature, exceptions)
return object : MethodVisitor(Settings.ASM_API, mv) {
override fun visitMethodInsn(
opcodeAndSource: Int,
owner: String?,
name: String?,
descriptor: String?,
owner: String,
name: String,
descriptor: String,
isInterface: Boolean
) {
if (name == "<init>") {
mv.visitMethodInsn(opcodeAndSource, owner, name, descriptor, isInterface)
owner?.let {
if (detector.isNonDeterministic(getOwnerClass(it).id)) {
detector.inserter.insertAfterNDInstanceConstructor(mv, currentClass!!)
}
if (detector.isNonDeterministicClass(owner)) {
detector.inserter.insertAfterNDInstanceConstructor(mv, currentClass)
}
return
}

getMethodAndOwnerId(owner, name, descriptor)?.let { (caller, method) ->
if (detector.isNonDeterministic(caller, method)) {
detector.inserter.insertBeforeNDMethod(mv, method)
mv.visitMethodInsn(opcodeAndSource, owner, name, descriptor, isInterface)
detector.inserter.insertAfterNDMethod(mv, method)
return
}
val (isND, isStatic) = if (opcodeAndSource == Opcodes.INVOKESTATIC) {
detector.isNonDeterministicStaticFunction(owner, name, descriptor) to true
} else {
detector.isNonDeterministicClass(owner) to false
}

if (isND) {
detector.inserter.insertBeforeNDMethod(mv, descriptor, isStatic)
mv.visitMethodInsn(opcodeAndSource, owner, name, descriptor, isInterface)
detector.inserter.insertAfterNDMethod(mv, owner, name, descriptor, isStatic)
} else {
mv.visitMethodInsn(opcodeAndSource, owner, name, descriptor, isInterface)
}

mv.visitMethodInsn(opcodeAndSource, owner, name, descriptor, isInterface)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,21 @@
package org.utbot.instrumentation.instrumentation.execution.ndd

import org.utbot.framework.plugin.api.ClassId
import org.utbot.framework.plugin.api.MethodId
import org.utbot.framework.plugin.api.util.id
import org.utbot.framework.plugin.api.util.isStatic
import kotlin.random.Random

class NonDeterministicDetector {
private val nonDeterministicStaticMethods: HashSet<MethodId> = HashSet()
private val nonDeterministicStaticMethods: HashSet<String> = HashSet()

private val nonDeterministicClasses: HashSet<ClassId> = buildList {
add(java.util.Random::class.java)
add(Random::class.java)
}.map { it.id }.toHashSet()
private val nonDeterministicClasses: HashSet<String> = buildList {
add("java/util/Random")
add("kotlin/random/Random")
}.toHashSet()

val inserter = NonDeterministicBytecodeInserter()

fun isNonDeterministic(caller: ClassId, method: MethodId): Boolean {
return if (method.isStatic) {
nonDeterministicStaticMethods.contains(method)
} else {
isNonDeterministic(caller)
}
fun isNonDeterministicStaticFunction(owner: String, name: String, descriptor: String): Boolean {
return nonDeterministicStaticMethods.contains("$owner $name$descriptor")
}

fun isNonDeterministic(clazz: ClassId): Boolean {
var cl: ClassId? = clazz
while (cl != null && !nonDeterministicClasses.contains(cl)) {
cl = cl.superclass?.id
}
return nonDeterministicClasses.contains(cl)
fun isNonDeterministicClass(clazz: String): Boolean {
return nonDeterministicClasses.contains(clazz)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ object NonDeterministicResultStorage {
nextInstanceNumber = 1
}

fun methodToSignature(methodId: MethodId): String {
return "${methodId.classId.name} ${methodId.signature}"
fun makeSignature(owner: String, name: String, descriptor: String): String {
return "$owner $name$descriptor"
}

fun signatureToMethod(signature: String): MethodId? {
Expand Down