diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt index ea7353a2b0..f0cd8a0174 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt @@ -466,6 +466,15 @@ object UtSettings : AbstractSettings(logger, defaultKeyForSettingsPath, defaultS */ var useSandbox by getBooleanProperty(true) + /** + * Transform bytecode in the instrumented process. + * + * If true, bytecode transformation will help fuzzing to find interesting input data, but the size of bytecode can increase. + * + * If false, bytecode won`t be changed. + */ + var useBytecodeTransformation by getBooleanProperty(false) + /** * Limit for number of generated tests per method (in each region) */ diff --git a/utbot-instrumentation-tests/src/test/java/org/utbot/examples/samples/transformation/StringMethodsCalls.java b/utbot-instrumentation-tests/src/test/java/org/utbot/examples/samples/transformation/StringMethodsCalls.java new file mode 100644 index 0000000000..d886d02cfa --- /dev/null +++ b/utbot-instrumentation-tests/src/test/java/org/utbot/examples/samples/transformation/StringMethodsCalls.java @@ -0,0 +1,45 @@ +package org.utbot.examples.samples.transformation; + +public class StringMethodsCalls { + public static boolean equalsWithEmptyString(String strToCompare) { + if (strToCompare.equals("")) { + return true; + } + return false; + } + + public static boolean equalsWithNotEmptyString(String strToCompare) { + if (strToCompare.equals("abc")) { + return true; + } + return false; + } + + public static boolean startsWithWithEmptyString(String strToCompare) { + if (strToCompare.startsWith("")) { + return true; + } + return false; + } + + public static boolean startsWithWithNotEmptyString(String strToCompare) { + if (strToCompare.startsWith("abc")) { + return true; + } + return false; + } + + public static boolean endsWithWithEmptyString(String strToCompare) { + if (strToCompare.endsWith("")) { + return true; + } + return false; + } + + public static boolean endsWithWithNotEmptyString(String strToCompare) { + if (strToCompare.endsWith("abc")) { + return true; + } + return false; + } +} \ No newline at end of file diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestBytecodeTransformation.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestBytecodeTransformation.kt new file mode 100644 index 0000000000..997a55c58b --- /dev/null +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestBytecodeTransformation.kt @@ -0,0 +1,130 @@ +package org.utbot.examples + +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.utbot.examples.samples.transformation.StringMethodsCalls +import org.utbot.instrumentation.execute +import org.utbot.instrumentation.instrumentation.transformation.BytecodeTransformation +import org.utbot.instrumentation.withInstrumentation + +class TestBytecodeTransformation { + lateinit var utContext: AutoCloseable + + @Test + fun testStringEqualsWithEmptyStringCall() { + withInstrumentation( + BytecodeTransformation.Factory, + StringMethodsCalls::class.java.protectionDomain.codeSource.location.path + ) { executor -> + val res1 = executor.execute(StringMethodsCalls::equalsWithEmptyString, arrayOf("")) + assertTrue(res1.getOrNull() as Boolean) + + val res2 = executor.execute(StringMethodsCalls::equalsWithEmptyString, arrayOf("abc")) + assertFalse(res2.getOrNull() as Boolean) + + val res3 = executor.execute(StringMethodsCalls::equalsWithEmptyString, arrayOf(null)) + assertTrue(res3.exceptionOrNull() is NullPointerException) + } + } + + @Test + fun testStringEqualsWithNotEmptyStringCall() { + withInstrumentation( + BytecodeTransformation.Factory, + StringMethodsCalls::class.java.protectionDomain.codeSource.location.path + ) { executor -> + val res1 = executor.execute(StringMethodsCalls::equalsWithNotEmptyString, arrayOf("")) + assertFalse(res1.getOrNull() as Boolean) + + val res2 = executor.execute(StringMethodsCalls::equalsWithNotEmptyString, arrayOf("abc")) + assertTrue(res2.getOrNull() as Boolean) + + val res3 = executor.execute(StringMethodsCalls::equalsWithNotEmptyString, arrayOf("abcd")) + assertFalse(res3.getOrNull() as Boolean) + + val res4 = executor.execute(StringMethodsCalls::equalsWithNotEmptyString, arrayOf(null)) + assertTrue(res4.exceptionOrNull() is NullPointerException) + } + } + + @Test + fun testStringStartsWithWithEmptyStringCall() { + withInstrumentation( + BytecodeTransformation.Factory, + StringMethodsCalls::class.java.protectionDomain.codeSource.location.path + ) { executor -> + val res1 = executor.execute(StringMethodsCalls::startsWithWithEmptyString, arrayOf("")) + assertTrue(res1.getOrNull() as Boolean) + + val res2 = executor.execute(StringMethodsCalls::startsWithWithEmptyString, arrayOf("abc")) + assertTrue(res2.getOrNull() as Boolean) + + val res3 = executor.execute(StringMethodsCalls::startsWithWithEmptyString, arrayOf(null)) + assertTrue(res3.exceptionOrNull() is NullPointerException) + } + } + + @Test + fun testStringStartsWithWithNotEmptyStringCall() { + withInstrumentation( + BytecodeTransformation.Factory, + StringMethodsCalls::class.java.protectionDomain.codeSource.location.path + ) { executor -> + val res1 = executor.execute(StringMethodsCalls::startsWithWithNotEmptyString, arrayOf("")) + assertFalse(res1.getOrNull() as Boolean) + + val res2 = executor.execute(StringMethodsCalls::startsWithWithNotEmptyString, arrayOf("abc")) + assertTrue(res2.getOrNull() as Boolean) + + val res3 = executor.execute(StringMethodsCalls::startsWithWithNotEmptyString, arrayOf("abcd")) + assertTrue(res3.getOrNull() as Boolean) + + val res4 = executor.execute(StringMethodsCalls::startsWithWithNotEmptyString, arrayOf("aabc")) + assertFalse(res4.getOrNull() as Boolean) + + val res5 = executor.execute(StringMethodsCalls::startsWithWithNotEmptyString, arrayOf(null)) + assertTrue(res5.exceptionOrNull() is NullPointerException) + } + } + + @Test + fun testStringEndsWithWithEmptyString() { + withInstrumentation( + BytecodeTransformation.Factory, + StringMethodsCalls::class.java.protectionDomain.codeSource.location.path + ) { executor -> + val res1 = executor.execute(StringMethodsCalls::endsWithWithEmptyString, arrayOf("")) + assertTrue(res1.getOrNull() as Boolean) + + val res2 = executor.execute(StringMethodsCalls::endsWithWithEmptyString, arrayOf("abc")) + assertTrue(res2.getOrNull() as Boolean) + + val res3 = executor.execute(StringMethodsCalls::endsWithWithEmptyString, arrayOf(null)) + assertTrue(res3.exceptionOrNull() is NullPointerException) + } + } + + @Test + fun testStringEndsWithWithNotEmptyString() { + withInstrumentation( + BytecodeTransformation.Factory, + StringMethodsCalls::class.java.protectionDomain.codeSource.location.path + ) { executor -> + val res1 = executor.execute(StringMethodsCalls::endsWithWithNotEmptyString, arrayOf("")) + assertFalse(res1.getOrNull() as Boolean) + + val res2 = executor.execute(StringMethodsCalls::endsWithWithNotEmptyString, arrayOf("abc")) + assertTrue(res2.getOrNull() as Boolean) + + val res3 = executor.execute(StringMethodsCalls::endsWithWithNotEmptyString, arrayOf("aabc")) + assertTrue(res3.getOrNull() as Boolean) + + val res4 = executor.execute(StringMethodsCalls::endsWithWithNotEmptyString, arrayOf("abcd")) + assertFalse(res4.getOrNull() as Boolean) + + val res5 = executor.execute(StringMethodsCalls::endsWithWithNotEmptyString, arrayOf(null)) + assertTrue(res5.exceptionOrNull() is NullPointerException) + } + } +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/agent/DynamicClassTransformer.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/agent/DynamicClassTransformer.kt index f89b09cade..c3e83d4694 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/agent/DynamicClassTransformer.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/agent/DynamicClassTransformer.kt @@ -9,6 +9,7 @@ import java.lang.instrument.ClassFileTransformer import java.nio.file.Paths import java.security.ProtectionDomain import kotlin.io.path.absolutePathString +import kotlin.properties.Delegates private val logger = getLogger() @@ -19,6 +20,7 @@ private val logger = getLogger() class DynamicClassTransformer : ClassFileTransformer { lateinit var transformer: ClassFileTransformer + var useBytecodeTransformation by Delegates.notNull() private val pathsToUserClasses = mutableSetOf() fun addUserPaths(paths: Iterable) { diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/SimpleUtExecutionInstrumentation.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/SimpleUtExecutionInstrumentation.kt index 7c21311731..98b30c11f8 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/SimpleUtExecutionInstrumentation.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/SimpleUtExecutionInstrumentation.kt @@ -4,6 +4,7 @@ import org.utbot.framework.plugin.api.EnvironmentModels import org.utbot.framework.plugin.api.FieldId import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.util.singleExecutableId +import org.utbot.instrumentation.agent.Agent import org.utbot.instrumentation.instrumentation.ArgumentList import org.utbot.instrumentation.instrumentation.InvokeInstrumentation import org.utbot.instrumentation.instrumentation.et.TraceHandler @@ -16,6 +17,7 @@ import org.utbot.instrumentation.instrumentation.execution.ndd.NonDeterministicD import org.utbot.instrumentation.instrumentation.execution.phases.PhasesController import org.utbot.instrumentation.instrumentation.instrumenter.Instrumenter import org.utbot.instrumentation.instrumentation.mock.MockClassVisitor +import org.utbot.instrumentation.instrumentation.transformation.BytecodeTransformer import java.security.ProtectionDomain import kotlin.reflect.jvm.javaMethod @@ -127,6 +129,12 @@ class SimpleUtExecutionInstrumentation( ): ByteArray { val instrumenter = Instrumenter(classfileBuffer, loader) + if (Agent.dynamicClassTransformer.useBytecodeTransformation) { + instrumenter.visitClass { writer -> + BytecodeTransformer(writer) + } + } + traceHandler.registerClass(className) instrumenter.visitInstructions(traceHandler.computeInstructionVisitor(className)) diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/BytecodeTransformation.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/BytecodeTransformation.kt new file mode 100644 index 0000000000..92d38616ae --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/BytecodeTransformation.kt @@ -0,0 +1,44 @@ +package org.utbot.instrumentation.instrumentation.transformation + +import org.utbot.framework.plugin.api.FieldId +import org.utbot.instrumentation.instrumentation.ArgumentList +import org.utbot.instrumentation.instrumentation.Instrumentation +import org.utbot.instrumentation.instrumentation.InvokeInstrumentation +import org.utbot.instrumentation.instrumentation.instrumenter.Instrumenter +import java.security.ProtectionDomain + +/** + * This instrumentation transforms bytecode and delegates invoking a given function to [InvokeInstrumentation]. + */ +class BytecodeTransformation : Instrumentation> { + private val invokeInstrumentation = InvokeInstrumentation() + + override fun invoke( + clazz: Class<*>, + methodSignature: String, + arguments: ArgumentList, + parameters: Any? + ): Result<*> = invokeInstrumentation.invoke(clazz, methodSignature, arguments, parameters) + + override fun getStaticField(fieldId: FieldId): Result<*> = invokeInstrumentation.getStaticField(fieldId) + + override fun transform( + loader: ClassLoader?, + className: String?, + classBeingRedefined: Class<*>?, + protectionDomain: ProtectionDomain?, + classfileBuffer: ByteArray + ): ByteArray { + val instrumenter = Instrumenter(classfileBuffer, loader) + + instrumenter.visitClass { writer -> + BytecodeTransformer(writer) + } + + return instrumenter.classByteCode + } + + object Factory : Instrumentation.Factory, BytecodeTransformation> { + override fun create(): BytecodeTransformation = BytecodeTransformation() + } +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/BytecodeTransformer.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/BytecodeTransformer.kt new file mode 100644 index 0000000000..6cdf1295df --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/BytecodeTransformer.kt @@ -0,0 +1,23 @@ +package org.utbot.instrumentation.instrumentation.transformation + +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.MethodVisitor +import org.utbot.instrumentation.Settings +import org.utbot.instrumentation.instrumentation.transformation.adapters.StringMethodsAdapter + +/** + * Main class for the transformation. + * Bytecode transformations will be combined in this class. + */ +class BytecodeTransformer(classVisitor: ClassVisitor) : ClassVisitor(Settings.ASM_API, classVisitor) { + override fun visitMethod( + access: Int, + name: String?, + descriptor: String?, + signature: String?, + exceptions: Array? + ): MethodVisitor { + val methodVisitor = cv.visitMethod(access, name, descriptor, signature, exceptions) + return StringMethodsAdapter(api, access, descriptor, methodVisitor) + } +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/adapters/PatternMethodAdapter.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/adapters/PatternMethodAdapter.kt new file mode 100644 index 0000000000..d35bf0d5a2 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/adapters/PatternMethodAdapter.kt @@ -0,0 +1,117 @@ +package org.utbot.instrumentation.instrumentation.transformation.adapters + +import org.objectweb.asm.Handle +import org.objectweb.asm.Label +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.commons.LocalVariablesSorter + + +/** + * Abstract class for the pattern method adapters. + * + * The idea of stateful bytecode transformation was described in the [ASM library user guide](https://asm.ow2.io/asm4-guide.pdf) in chapter 3.2.5. + */ +abstract class PatternMethodAdapter( + api: Int, + access: Int, + descriptor: String?, + methodVisitor: MethodVisitor +) : LocalVariablesSorter(api, access, descriptor, methodVisitor) { + protected abstract fun resetState() + + override fun visitFrame(type: Int, numLocal: Int, local: Array?, numStack: Int, stack: Array?) { + resetState() + mv.visitFrame(type, numLocal, local, numStack, stack) + } + + override fun visitInsn(opcode: Int) { + resetState() + mv.visitInsn(opcode) + } + + override fun visitIntInsn(opcode: Int, operand: Int) { + resetState() + mv.visitIntInsn(opcode, operand) + } + + override fun visitVarInsn(opcode: Int, localVariable: Int) { + resetState() + mv.visitVarInsn(opcode, localVariable) + } + + override fun visitTypeInsn(opcode: Int, type: String?) { + resetState() + mv.visitTypeInsn(opcode, type) + } + + override fun visitFieldInsn(opcode: Int, owner: String?, name: String?, descriptor: String?) { + resetState() + mv.visitFieldInsn(opcode, owner, name, descriptor) + } + + override fun visitMethodInsn( + opcode: Int, + owner: String?, + name: String?, + descriptor: String?, + isInterface: Boolean + ) { + resetState() + mv.visitMethodInsn(opcode, owner, name, descriptor, isInterface) + } + + override fun visitInvokeDynamicInsn( + name: String?, + descriptor: String?, + bootstrapMethodHandle: Handle?, + vararg bootstrapMethodArguments: Any? + ) { + resetState() + mv.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, *bootstrapMethodArguments) + } + + override fun visitJumpInsn(opcode: Int, label: Label?) { + resetState() + mv.visitJumpInsn(opcode, label) + } + + override fun visitLabel(label: Label?) { + resetState() + mv.visitLabel(label) + } + + override fun visitLdcInsn(value: Any?) { + resetState() + mv.visitLdcInsn(value) + } + + override fun visitIincInsn(`var`: Int, increment: Int) { + resetState() + mv.visitIincInsn(`var`, increment) + } + + override fun visitTableSwitchInsn(min: Int, max: Int, dflt: Label?, vararg labels: Label?) { + resetState() + mv.visitTableSwitchInsn(min, max, dflt, *labels) + } + + override fun visitLookupSwitchInsn(dflt: Label?, keys: IntArray?, labels: Array?) { + resetState() + mv.visitLookupSwitchInsn(dflt, keys, labels) + } + + override fun visitMultiANewArrayInsn(descriptor: String?, numDimensions: Int) { + resetState() + mv.visitMultiANewArrayInsn(descriptor, numDimensions) + } + + override fun visitTryCatchBlock(start: Label?, end: Label?, handler: Label?, type: String?) { + resetState() + mv.visitTryCatchBlock(start, end, handler, type) + } + + override fun visitMaxs(maxStack: Int, maxLocals: Int) { + resetState() + mv.visitMaxs(maxStack, maxLocals) + } +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/adapters/StringMethodsAdapter.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/adapters/StringMethodsAdapter.kt new file mode 100644 index 0000000000..b73e9d552f --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/adapters/StringMethodsAdapter.kt @@ -0,0 +1,191 @@ +package org.utbot.instrumentation.instrumentation.transformation.adapters + +import org.objectweb.asm.Label +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes +import org.objectweb.asm.Type +import kotlin.properties.Delegates + +/** + * Class for transforming an if statement with call of the [String.equals], [String.startsWith] or [String.endsWith] method + * with a constant string into a sequence of comparisons of each char of the string with each char of the constant string. + */ +class StringMethodsAdapter( + api: Int, + access: Int, + descriptor: String?, + methodVisitor: MethodVisitor +) : PatternMethodAdapter(api, access, descriptor, methodVisitor) { + private enum class State { + SEEN_NOTHING, + SEEN_ALOAD, + SEEN_LDC_STRING_CONST, + SEEN_INVOKEVIRTUAL_STRING_EQUALS, + SEEN_INVOKEVIRTUAL_STRING_STARTSWITH, + SEEN_INVOKEVIRTUAL_STRING_ENDSWITH + } + + private var state: State = State.SEEN_NOTHING + + private var indexOfLocalVariable by Delegates.notNull() + private lateinit var constString: String + + override fun resetState() { + when (state) { + State.SEEN_ALOAD -> mv.visitVarInsn(Opcodes.ALOAD, indexOfLocalVariable) + State.SEEN_LDC_STRING_CONST -> { + mv.visitVarInsn(Opcodes.ALOAD, indexOfLocalVariable) + mv.visitLdcInsn(constString) + } + + State.SEEN_INVOKEVIRTUAL_STRING_EQUALS -> { + mv.visitVarInsn(Opcodes.ALOAD, indexOfLocalVariable) + mv.visitLdcInsn(constString) + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false) + } + + State.SEEN_INVOKEVIRTUAL_STRING_STARTSWITH -> { + mv.visitVarInsn(Opcodes.ALOAD, indexOfLocalVariable) + mv.visitLdcInsn(constString) + mv.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + "java/lang/String", + "startsWith", + "(Ljava/lang/String;)Z", + false + ) + } + + State.SEEN_INVOKEVIRTUAL_STRING_ENDSWITH -> { + mv.visitVarInsn(Opcodes.ALOAD, indexOfLocalVariable) + mv.visitLdcInsn(constString) + mv.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + "java/lang/String", + "endsWith", + "(Ljava/lang/String;)Z", + false + ) + } + + else -> {} + } + state = State.SEEN_NOTHING + } + + override fun visitVarInsn(opcode: Int, localVariable: Int) { + if (state == State.SEEN_NOTHING && opcode == Opcodes.ALOAD) { + state = State.SEEN_ALOAD + indexOfLocalVariable = localVariable + return + } + resetState() + if (opcode == Opcodes.ALOAD) { + state = State.SEEN_ALOAD + indexOfLocalVariable = localVariable + return + } + mv.visitVarInsn(opcode, localVariable) + } + + override fun visitMethodInsn( + opcode: Int, + owner: String?, + name: String?, + descriptor: String?, + isInterface: Boolean + ) { + if (state == State.SEEN_LDC_STRING_CONST && !isInterface && opcode == Opcodes.INVOKEVIRTUAL && owner == "java/lang/String") { + when { + name == "equals" && descriptor == "(Ljava/lang/Object;)Z" -> { + state = State.SEEN_INVOKEVIRTUAL_STRING_EQUALS + return + } + + name == "startsWith" && descriptor == "(Ljava/lang/String;)Z" -> { + state = State.SEEN_INVOKEVIRTUAL_STRING_STARTSWITH + return + } + + name == "endsWith" && descriptor == "(Ljava/lang/String;)Z" -> { + state = State.SEEN_INVOKEVIRTUAL_STRING_ENDSWITH + return + } + } + } + resetState() + mv.visitMethodInsn(opcode, owner, name, descriptor, isInterface) + } + + override fun visitLdcInsn(value: Any?) { + if (state == State.SEEN_ALOAD && value is String) { + state = State.SEEN_LDC_STRING_CONST + constString = value + return + } + resetState() + mv.visitLdcInsn(value) + } + + override fun visitJumpInsn(opcode: Int, label: Label?) { + if (setOf( + State.SEEN_INVOKEVIRTUAL_STRING_EQUALS, + State.SEEN_INVOKEVIRTUAL_STRING_STARTSWITH, + State.SEEN_INVOKEVIRTUAL_STRING_ENDSWITH + ).any { state == it } && opcode == Opcodes.IFEQ + ) { + // code transformation + // if (str.length() == constString.length()) for equals method + // if (str.length() >= constString.length()) for startsWith and endsWith methods + mv.visitVarInsn(Opcodes.ALOAD, indexOfLocalVariable) + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "length", "()I", false) + mv.visitIntInsn(Opcodes.BIPUSH, constString.length) + if (state == State.SEEN_INVOKEVIRTUAL_STRING_EQUALS) { + mv.visitJumpInsn(Opcodes.IF_ICMPNE, label) + } else { + mv.visitJumpInsn(Opcodes.IF_ICMPLT, label) + } + + if (constString.isEmpty()) { + state = State.SEEN_NOTHING + return + } + + if (state == State.SEEN_INVOKEVIRTUAL_STRING_ENDSWITH) { + // int length = str.length() + mv.visitLabel(Label()) + mv.visitVarInsn(Opcodes.ALOAD, indexOfLocalVariable) + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "length", "()I", false) + val length = newLocal(Type.INT_TYPE) + mv.visitVarInsn(Opcodes.ISTORE, length) + + // reverse constant string to compare chars from the end + constString.reversed().forEachIndexed { index, c -> + // if (str.charAt(length - (index + 1)) == c) + mv.visitLabel(Label()) + mv.visitVarInsn(Opcodes.ALOAD, indexOfLocalVariable) + mv.visitVarInsn(Opcodes.ILOAD, length) + mv.visitIntInsn(Opcodes.BIPUSH, index + 1) + mv.visitInsn(Opcodes.ISUB) + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "charAt", "(I)C", false) + mv.visitIntInsn(Opcodes.BIPUSH, c.code) + mv.visitJumpInsn(Opcodes.IF_ICMPNE, label) + } + } else { + constString.forEachIndexed { index, c -> + // if (str.charAt(index) == c) + mv.visitLabel(Label()) + mv.visitVarInsn(Opcodes.ALOAD, indexOfLocalVariable) + mv.visitIntInsn(Opcodes.BIPUSH, index) + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "charAt", "(I)C", false) + mv.visitIntInsn(Opcodes.BIPUSH, c.code) + mv.visitJumpInsn(Opcodes.IF_ICMPNE, label) + } + } + state = State.SEEN_NOTHING + return + } + resetState() + mv.visitJumpInsn(opcode, label) + } +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/InstrumentedProcessMain.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/InstrumentedProcessMain.kt index 58618ac593..841f1d4ca3 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/InstrumentedProcessMain.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/InstrumentedProcessMain.kt @@ -148,6 +148,7 @@ private fun InstrumentedProcessModel.setup(kryoHelper: KryoHelper, watchdog: Idl HandlerClassesLoader.addUrls(instrumentationFactory.additionalRuntimeClasspath) instrumentation = instrumentationFactory.create() logger.debug { "instrumentation - ${instrumentation.javaClass.name} " } + Agent.dynamicClassTransformer.useBytecodeTransformation = params.useBytecodeTransformation Agent.dynamicClassTransformer.transformer = instrumentation Agent.dynamicClassTransformer.addUserPaths(pathsToUserClasses) } diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/InstrumentedProcess.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/InstrumentedProcess.kt index baef39ac5f..299fee8ff0 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/InstrumentedProcess.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/InstrumentedProcess.kt @@ -131,7 +131,8 @@ class InstrumentedProcess private constructor( logger.trace("sending instrumentation") proc.instrumentedProcessModel.setInstrumentation.startSuspending( proc.lifetime, SetInstrumentationParams( - proc.kryoHelper.writeObject(instrumentationFactory) + proc.kryoHelper.writeObject(instrumentationFactory), + UtSettings.useBytecodeTransformation ) ) logger.trace("start commands sent") diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/generated/InstrumentedProcessModel.Generated.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/generated/InstrumentedProcessModel.Generated.kt index e08ed48ba6..5500b9121b 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/generated/InstrumentedProcessModel.Generated.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/generated/InstrumentedProcessModel.Generated.kt @@ -67,7 +67,7 @@ class InstrumentedProcessModel private constructor( } - const val serializationHash = -3572666434834334555L + const val serializationHash = 2667021258656776274L } override val serializersOwner: ISerializersOwner get() = InstrumentedProcessModel @@ -260,7 +260,7 @@ data class AddPathsParams ( /** - * #### Generated from [InstrumentedProcessModel.kt:28] + * #### Generated from [InstrumentedProcessModel.kt:29] */ data class CollectCoverageParams ( val clazz: ByteArray @@ -317,7 +317,7 @@ data class CollectCoverageParams ( /** - * #### Generated from [InstrumentedProcessModel.kt:32] + * #### Generated from [InstrumentedProcessModel.kt:33] */ data class CollectCoverageResult ( val coverageInfo: ByteArray @@ -374,7 +374,7 @@ data class CollectCoverageResult ( /** - * #### Generated from [InstrumentedProcessModel.kt:36] + * #### Generated from [InstrumentedProcessModel.kt:37] */ data class ComputeStaticFieldParams ( val fieldId: ByteArray @@ -431,7 +431,7 @@ data class ComputeStaticFieldParams ( /** - * #### Generated from [InstrumentedProcessModel.kt:40] + * #### Generated from [InstrumentedProcessModel.kt:41] */ data class ComputeStaticFieldResult ( val result: ByteArray @@ -488,7 +488,7 @@ data class ComputeStaticFieldResult ( /** - * #### Generated from [InstrumentedProcessModel.kt:44] + * #### Generated from [InstrumentedProcessModel.kt:45] */ data class GetSpringBeanParams ( val beanName: String @@ -545,7 +545,7 @@ data class GetSpringBeanParams ( /** - * #### Generated from [InstrumentedProcessModel.kt:48] + * #### Generated from [InstrumentedProcessModel.kt:49] */ data class GetSpringBeanResult ( val beanModel: ByteArray @@ -602,7 +602,7 @@ data class GetSpringBeanResult ( /** - * #### Generated from [InstrumentedProcessModel.kt:52] + * #### Generated from [InstrumentedProcessModel.kt:53] */ data class GetSpringRepositoriesParams ( val classId: ByteArray @@ -659,7 +659,7 @@ data class GetSpringRepositoriesParams ( /** - * #### Generated from [InstrumentedProcessModel.kt:56] + * #### Generated from [InstrumentedProcessModel.kt:57] */ data class GetSpringRepositoriesResult ( val springRepositoryIds: ByteArray @@ -716,7 +716,7 @@ data class GetSpringRepositoriesResult ( /** - * #### Generated from [InstrumentedProcessModel.kt:17] + * #### Generated from [InstrumentedProcessModel.kt:18] */ data class InvokeMethodCommandParams ( val classname: String, @@ -791,7 +791,7 @@ data class InvokeMethodCommandParams ( /** - * #### Generated from [InstrumentedProcessModel.kt:24] + * #### Generated from [InstrumentedProcessModel.kt:25] */ data class InvokeMethodCommandResult ( val result: ByteArray @@ -851,7 +851,8 @@ data class InvokeMethodCommandResult ( * #### Generated from [InstrumentedProcessModel.kt:13] */ data class SetInstrumentationParams ( - val instrumentation: ByteArray + val instrumentation: ByteArray, + val useBytecodeTransformation: Boolean ) : IPrintable { //companion @@ -861,11 +862,13 @@ data class SetInstrumentationParams ( @Suppress("UNCHECKED_CAST") override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): SetInstrumentationParams { val instrumentation = buffer.readByteArray() - return SetInstrumentationParams(instrumentation) + val useBytecodeTransformation = buffer.readBool() + return SetInstrumentationParams(instrumentation, useBytecodeTransformation) } override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: SetInstrumentationParams) { buffer.writeByteArray(value.instrumentation) + buffer.writeBool(value.useBytecodeTransformation) } @@ -882,6 +885,7 @@ data class SetInstrumentationParams ( other as SetInstrumentationParams if (!(instrumentation contentEquals other.instrumentation)) return false + if (useBytecodeTransformation != other.useBytecodeTransformation) return false return true } @@ -889,6 +893,7 @@ data class SetInstrumentationParams ( override fun hashCode(): Int { var __r = 0 __r = __r*31 + instrumentation.contentHashCode() + __r = __r*31 + useBytecodeTransformation.hashCode() return __r } //pretty print @@ -896,6 +901,7 @@ data class SetInstrumentationParams ( printer.println("SetInstrumentationParams (") printer.indent { print("instrumentation = "); instrumentation.print(printer); println() + print("useBytecodeTransformation = "); useBytecodeTransformation.print(printer); println() } printer.print(")") } @@ -905,7 +911,7 @@ data class SetInstrumentationParams ( /** - * #### Generated from [InstrumentedProcessModel.kt:60] + * #### Generated from [InstrumentedProcessModel.kt:61] */ data class TryLoadingSpringContextResult ( val springContextLoadingResult: ByteArray diff --git a/utbot-rd/src/main/rdgen/org/utbot/rd/models/InstrumentedProcessModel.kt b/utbot-rd/src/main/rdgen/org/utbot/rd/models/InstrumentedProcessModel.kt index cdefe5c103..db0d827687 100644 --- a/utbot-rd/src/main/rdgen/org/utbot/rd/models/InstrumentedProcessModel.kt +++ b/utbot-rd/src/main/rdgen/org/utbot/rd/models/InstrumentedProcessModel.kt @@ -12,6 +12,7 @@ object InstrumentedProcessModel : Ext(InstrumentedProcessRoot) { val SetInstrumentationParams = structdef { field("instrumentation", array(PredefinedType.byte)) + field("useBytecodeTransformation", PredefinedType.bool) } val InvokeMethodCommandParams = structdef {