From 2448a5255a50674ab19616d625bd7084900597c7 Mon Sep 17 00:00:00 2001 From: Pavel Egipti Date: Tue, 25 Jul 2023 13:36:51 +0300 Subject: [PATCH 1/6] Add bytecode transformation for if statement with call of String.equals method --- .../execution/UtExecutionInstrumentation.kt | 10 ++ .../transformation/BytecodeTransformer.kt | 23 ++++ .../adapters/PatternMethodAdapter.kt | 109 +++++++++++++++++ .../adapters/StringEqualsMethodAdapter.kt | 113 ++++++++++++++++++ 4 files changed, 255 insertions(+) create mode 100644 utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/BytecodeTransformer.kt create mode 100644 utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/adapters/PatternMethodAdapter.kt create mode 100644 utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/adapters/StringEqualsMethodAdapter.kt diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/UtExecutionInstrumentation.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/UtExecutionInstrumentation.kt index 824393c1bb..82c3a1ea0e 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/UtExecutionInstrumentation.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/UtExecutionInstrumentation.kt @@ -1,5 +1,6 @@ package org.utbot.instrumentation.instrumentation.execution +import org.objectweb.asm.ClassWriter import org.utbot.framework.UtSettings import org.utbot.framework.plugin.api.* import org.utbot.framework.plugin.api.util.singleExecutableId @@ -14,8 +15,10 @@ import org.utbot.instrumentation.instrumentation.execution.context.SimpleInstrum import org.utbot.instrumentation.instrumentation.execution.ndd.NonDeterministicClassVisitor import org.utbot.instrumentation.instrumentation.execution.ndd.NonDeterministicDetector import org.utbot.instrumentation.instrumentation.execution.phases.PhasesController +import org.utbot.instrumentation.instrumentation.instrumenter.ClassVisitorBuilder 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 @@ -169,6 +172,13 @@ object UtExecutionInstrumentation : Instrumentation { ): ByteArray { val instrumenter = Instrumenter(classfileBuffer, loader) + instrumenter.visitClass(object : ClassVisitorBuilder { + override val writerFlags: Int + get() = 0 + + override fun build(writer: ClassWriter): BytecodeTransformer = BytecodeTransformer(writer) + }) + traceHandler.registerClass(className) instrumenter.visitInstructions(traceHandler.computeInstructionVisitor(className)) 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..686feaef82 --- /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.StringEqualsMethodAdapter + +/** + * 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 StringEqualsMethodAdapter(api, 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..e3838d2871 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/adapters/PatternMethodAdapter.kt @@ -0,0 +1,109 @@ +package org.utbot.instrumentation.instrumentation.transformation.adapters + +import org.objectweb.asm.Handle +import org.objectweb.asm.Label +import org.objectweb.asm.MethodVisitor + + +/** + * Abstract class for the pattern method adapters. + */ +abstract class PatternMethodAdapter(api: Int, methodVisitor: MethodVisitor) : MethodVisitor(api, 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, `var`: Int) { + resetState() + mv.visitVarInsn(opcode, `var`) + } + + 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/StringEqualsMethodAdapter.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/adapters/StringEqualsMethodAdapter.kt new file mode 100644 index 0000000000..786c761517 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/adapters/StringEqualsMethodAdapter.kt @@ -0,0 +1,113 @@ +package org.utbot.instrumentation.instrumentation.transformation.adapters + +import org.objectweb.asm.Label +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes +import kotlin.properties.Delegates + +/** + * Class for transforming an if statement with call of the [String.equals] method with a constant string into + * a sequence of comparisons of each char of the string with each char of the constant string. + */ +class StringEqualsMethodAdapter(api: Int, methodVisitor: MethodVisitor) : PatternMethodAdapter(api, methodVisitor) { + private enum class State { + SEEN_NOTHING, + SEEN_ALOAD, + SEEN_LDC_STRING_CONST, + SEEN_INVOKEVIRTUAL_STRING_EQUALS, + } + + 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) + } + + else -> {} + } + state = State.SEEN_NOTHING + } + + override fun visitVarInsn(opcode: Int, `var`: Int) { + if (state == State.SEEN_NOTHING && opcode == Opcodes.ALOAD) { + state = State.SEEN_ALOAD + indexOfLocalVariable = `var` + return + } + resetState() + if (opcode == Opcodes.ALOAD) { + state = State.SEEN_ALOAD + indexOfLocalVariable = `var` + return + } + mv.visitVarInsn(opcode, `var`) + } + + override fun visitMethodInsn( + opcode: Int, + owner: String?, + name: String?, + descriptor: String?, + isInterface: Boolean + ) { + if (state == State.SEEN_LDC_STRING_CONST) { + if (!isInterface && opcode == Opcodes.INVOKEVIRTUAL && owner == "java/lang/String" && name == "equals" && descriptor == "(Ljava/lang/Object;)Z") { + state = State.SEEN_INVOKEVIRTUAL_STRING_EQUALS + 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 (state == State.SEEN_INVOKEVIRTUAL_STRING_EQUALS && opcode == Opcodes.IFEQ) { + state = State.SEEN_NOTHING + // code transformation + // if (str.length() == constString.length()) + mv.visitVarInsn(Opcodes.ALOAD, indexOfLocalVariable) + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "length", "()I", false) + mv.visitIntInsn(Opcodes.BIPUSH, constString.length) + mv.visitJumpInsn(Opcodes.IF_ICMPNE, label) + if (constString.isEmpty()) { + return + } + constString.forEachIndexed { index, c -> + // if (str.charAt(index) == c) + val l = Label() + mv.visitLabel(l) + 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) + } + return + } + resetState() + mv.visitJumpInsn(opcode, label) + } +} \ No newline at end of file From eefe12903468868e467c6e70ab46ce7a5c50bd6d Mon Sep 17 00:00:00 2001 From: Pavel Egipti Date: Tue, 25 Jul 2023 17:34:31 +0300 Subject: [PATCH 2/6] Add bytecode transformation for String.startsWith and String.endsWith methods --- .../transformation/BytecodeTransformer.kt | 2 +- .../adapters/PatternMethodAdapter.kt | 8 +- .../adapters/StringEqualsMethodAdapter.kt | 118 +++++++++++++++--- 3 files changed, 108 insertions(+), 20 deletions(-) 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 index 686feaef82..e094d3a07d 100644 --- 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 @@ -18,6 +18,6 @@ class BytecodeTransformer(classVisitor: ClassVisitor) : ClassVisitor(Settings.AS exceptions: Array? ): MethodVisitor { val methodVisitor = cv.visitMethod(access, name, descriptor, signature, exceptions) - return StringEqualsMethodAdapter(api, methodVisitor) + return StringEqualsMethodAdapter(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 index e3838d2871..d982e51286 100644 --- 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 @@ -3,12 +3,18 @@ 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. */ -abstract class PatternMethodAdapter(api: Int, methodVisitor: MethodVisitor) : MethodVisitor(api, methodVisitor) { +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?) { diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/adapters/StringEqualsMethodAdapter.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/adapters/StringEqualsMethodAdapter.kt index 786c761517..e126b96a26 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/adapters/StringEqualsMethodAdapter.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/adapters/StringEqualsMethodAdapter.kt @@ -3,18 +3,26 @@ 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] method with a constant string into - * a sequence of comparisons of each char of the string with each char of the constant string. + * 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 StringEqualsMethodAdapter(api: Int, methodVisitor: MethodVisitor) : PatternMethodAdapter(api, methodVisitor) { +class StringEqualsMethodAdapter( + 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 @@ -36,6 +44,30 @@ class StringEqualsMethodAdapter(api: Int, methodVisitor: MethodVisitor) : Patter 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 @@ -63,10 +95,22 @@ class StringEqualsMethodAdapter(api: Int, methodVisitor: MethodVisitor) : Patter descriptor: String?, isInterface: Boolean ) { - if (state == State.SEEN_LDC_STRING_CONST) { - if (!isInterface && opcode == Opcodes.INVOKEVIRTUAL && owner == "java/lang/String" && name == "equals" && descriptor == "(Ljava/lang/Object;)Z") { - state = State.SEEN_INVOKEVIRTUAL_STRING_EQUALS - return + 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() @@ -84,30 +128,68 @@ class StringEqualsMethodAdapter(api: Int, methodVisitor: MethodVisitor) : Patter } override fun visitJumpInsn(opcode: Int, label: Label?) { - if (state == State.SEEN_INVOKEVIRTUAL_STRING_EQUALS && opcode == Opcodes.IFEQ) { + if (setOf( + State.SEEN_INVOKEVIRTUAL_STRING_EQUALS, + State.SEEN_INVOKEVIRTUAL_STRING_STARTSWITH, + State.SEEN_INVOKEVIRTUAL_STRING_ENDSWITH + ).any { it == state } && opcode == Opcodes.IFEQ + ) { state = State.SEEN_NOTHING // code transformation - // if (str.length() == constString.length()) + // if (str.length() >= constString.length()) for startsWith and endsWith methods + // if (str.length() == constString.length()) for equals method mv.visitVarInsn(Opcodes.ALOAD, indexOfLocalVariable) mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "length", "()I", false) mv.visitIntInsn(Opcodes.BIPUSH, constString.length) - mv.visitJumpInsn(Opcodes.IF_ICMPNE, label) + if (state == State.SEEN_INVOKEVIRTUAL_STRING_EQUALS) { + mv.visitJumpInsn(Opcodes.IF_ICMPNE, label) + } else { + mv.visitJumpInsn(Opcodes.IF_ICMPLT, label) + } + if (constString.isEmpty()) { return } - constString.forEachIndexed { index, c -> - // if (str.charAt(index) == c) - val l = Label() - mv.visitLabel(l) + + if (state == State.SEEN_INVOKEVIRTUAL_STRING_ENDSWITH) { + // int length = str.length() + 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) + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "length", "()I", false) + val length = newLocal(Type.INT_TYPE) + mv.visitVarInsn(Opcodes.ISTORE, length) + + constString.forEachIndexed { index, c -> + // if (str.charAt(length - (constString.length() - index)) == c) + mv.visitLabel(Label()) + mv.visitVarInsn(Opcodes.ALOAD, indexOfLocalVariable) + mv.visitVarInsn(Opcodes.ILOAD, length) + mv.visitIntInsn(Opcodes.BIPUSH, constString.length - index) + 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) + } } return } resetState() mv.visitJumpInsn(opcode, label) } + + override fun visitMaxs(maxStack: Int, maxLocals: Int) { + resetState() + // TODO optimize + mv.visitMaxs(maxStack + 1, maxLocals + 1) + } } \ No newline at end of file From b1d49eab52123cce61a96d3897b2c25a06379b0e Mon Sep 17 00:00:00 2001 From: Pavel Egipti Date: Wed, 26 Jul 2023 17:39:53 +0300 Subject: [PATCH 3/6] Fix bug and add tests --- .../transformation/StringMethodsCalls.java | 45 ++++++ .../examples/TestBytecodeTransformation.kt | 130 ++++++++++++++++++ .../SimpleUtExecutionInstrumentation.kt | 3 + .../transformation/BytecodeTransformation.kt | 52 +++++++ .../transformation/BytecodeTransformer.kt | 4 +- ...thodAdapter.kt => StringMethodsAdapter.kt} | 10 +- 6 files changed, 237 insertions(+), 7 deletions(-) create mode 100644 utbot-instrumentation-tests/src/test/java/org/utbot/examples/samples/transformation/StringMethodsCalls.java create mode 100644 utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestBytecodeTransformation.kt create mode 100644 utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/BytecodeTransformation.kt rename utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/adapters/{StringEqualsMethodAdapter.kt => StringMethodsAdapter.kt} (98%) 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/instrumentation/execution/SimpleUtExecutionInstrumentation.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/SimpleUtExecutionInstrumentation.kt index 486ab56a85..8673ae6b93 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 @@ -134,6 +134,9 @@ class SimpleUtExecutionInstrumentation( override val writerFlags: Int get() = 0 + override val readerParsingOptions: Int + get() = 0 + override fun build(writer: ClassWriter): BytecodeTransformer = BytecodeTransformer(writer) }) 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..4e4ccbfa03 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/BytecodeTransformation.kt @@ -0,0 +1,52 @@ +package org.utbot.instrumentation.instrumentation.transformation + +import org.objectweb.asm.ClassWriter +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.ClassVisitorBuilder +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(object : ClassVisitorBuilder { + override val writerFlags: Int + get() = 0 + + override val readerParsingOptions: Int + get() = 0 + + override fun build(writer: ClassWriter): BytecodeTransformer = 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 index e094d3a07d..6cdf1295df 100644 --- 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 @@ -3,7 +3,7 @@ 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.StringEqualsMethodAdapter +import org.utbot.instrumentation.instrumentation.transformation.adapters.StringMethodsAdapter /** * Main class for the transformation. @@ -18,6 +18,6 @@ class BytecodeTransformer(classVisitor: ClassVisitor) : ClassVisitor(Settings.AS exceptions: Array? ): MethodVisitor { val methodVisitor = cv.visitMethod(access, name, descriptor, signature, exceptions) - return StringEqualsMethodAdapter(api, access, descriptor, methodVisitor) + 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/StringEqualsMethodAdapter.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/adapters/StringMethodsAdapter.kt similarity index 98% rename from utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/adapters/StringEqualsMethodAdapter.kt rename to utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/adapters/StringMethodsAdapter.kt index e126b96a26..b3d55288a1 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/adapters/StringEqualsMethodAdapter.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/adapters/StringMethodsAdapter.kt @@ -10,7 +10,7 @@ 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 StringEqualsMethodAdapter( +class StringMethodsAdapter( api: Int, access: Int, descriptor: String?, @@ -132,12 +132,11 @@ class StringEqualsMethodAdapter( State.SEEN_INVOKEVIRTUAL_STRING_EQUALS, State.SEEN_INVOKEVIRTUAL_STRING_STARTSWITH, State.SEEN_INVOKEVIRTUAL_STRING_ENDSWITH - ).any { it == state } && opcode == Opcodes.IFEQ + ).any { state == it } && opcode == Opcodes.IFEQ ) { - state = State.SEEN_NOTHING // code transformation - // if (str.length() >= constString.length()) for startsWith and endsWith methods // 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) @@ -148,6 +147,7 @@ class StringEqualsMethodAdapter( } if (constString.isEmpty()) { + state = State.SEEN_NOTHING return } @@ -181,6 +181,7 @@ class StringEqualsMethodAdapter( mv.visitJumpInsn(Opcodes.IF_ICMPNE, label) } } + state = State.SEEN_NOTHING return } resetState() @@ -189,7 +190,6 @@ class StringEqualsMethodAdapter( override fun visitMaxs(maxStack: Int, maxLocals: Int) { resetState() - // TODO optimize mv.visitMaxs(maxStack + 1, maxLocals + 1) } } \ No newline at end of file From 4ec2b2b20ab7bbd783423dd36a7ec8396cf7d69f Mon Sep 17 00:00:00 2001 From: Pavel Egipti Date: Mon, 31 Jul 2023 15:14:02 +0300 Subject: [PATCH 4/6] Fix bug with computing maximum stack size and the maximum number of local variables of the method --- .../SimpleUtExecutionInstrumentation.kt | 14 +++---------- .../transformation/BytecodeTransformation.kt | 14 +++---------- .../adapters/PatternMethodAdapter.kt | 6 ++++-- .../adapters/StringMethodsAdapter.kt | 20 ++++++++----------- 4 files changed, 18 insertions(+), 36 deletions(-) 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 8673ae6b93..03ffd0a607 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 @@ -1,6 +1,5 @@ package org.utbot.instrumentation.instrumentation.execution -import org.objectweb.asm.ClassWriter import org.utbot.framework.plugin.api.EnvironmentModels import org.utbot.framework.plugin.api.FieldId import org.utbot.framework.plugin.api.UtModel @@ -15,7 +14,6 @@ import org.utbot.instrumentation.instrumentation.execution.context.SimpleInstrum import org.utbot.instrumentation.instrumentation.execution.ndd.NonDeterministicClassVisitor import org.utbot.instrumentation.instrumentation.execution.ndd.NonDeterministicDetector import org.utbot.instrumentation.instrumentation.execution.phases.PhasesController -import org.utbot.instrumentation.instrumentation.instrumenter.ClassVisitorBuilder import org.utbot.instrumentation.instrumentation.instrumenter.Instrumenter import org.utbot.instrumentation.instrumentation.mock.MockClassVisitor import org.utbot.instrumentation.instrumentation.transformation.BytecodeTransformer @@ -130,15 +128,9 @@ class SimpleUtExecutionInstrumentation( ): ByteArray { val instrumenter = Instrumenter(classfileBuffer, loader) - instrumenter.visitClass(object : ClassVisitorBuilder { - override val writerFlags: Int - get() = 0 - - override val readerParsingOptions: Int - get() = 0 - - override fun build(writer: ClassWriter): BytecodeTransformer = BytecodeTransformer(writer) - }) + 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 index 4e4ccbfa03..92d38616ae 100644 --- 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 @@ -1,11 +1,9 @@ package org.utbot.instrumentation.instrumentation.transformation -import org.objectweb.asm.ClassWriter 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.ClassVisitorBuilder import org.utbot.instrumentation.instrumentation.instrumenter.Instrumenter import java.security.ProtectionDomain @@ -33,15 +31,9 @@ class BytecodeTransformation : Instrumentation> { ): ByteArray { val instrumenter = Instrumenter(classfileBuffer, loader) - instrumenter.visitClass(object : ClassVisitorBuilder { - override val writerFlags: Int - get() = 0 - - override val readerParsingOptions: Int - get() = 0 - - override fun build(writer: ClassWriter): BytecodeTransformer = BytecodeTransformer(writer) - }) + instrumenter.visitClass { writer -> + BytecodeTransformer(writer) + } return instrumenter.classByteCode } 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 index d982e51286..d35bf0d5a2 100644 --- 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 @@ -8,6 +8,8 @@ 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, @@ -32,9 +34,9 @@ abstract class PatternMethodAdapter( mv.visitIntInsn(opcode, operand) } - override fun visitVarInsn(opcode: Int, `var`: Int) { + override fun visitVarInsn(opcode: Int, localVariable: Int) { resetState() - mv.visitVarInsn(opcode, `var`) + mv.visitVarInsn(opcode, localVariable) } override fun visitTypeInsn(opcode: Int, type: String?) { 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 index b3d55288a1..b73e9d552f 100644 --- 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 @@ -73,19 +73,19 @@ class StringMethodsAdapter( state = State.SEEN_NOTHING } - override fun visitVarInsn(opcode: Int, `var`: Int) { + override fun visitVarInsn(opcode: Int, localVariable: Int) { if (state == State.SEEN_NOTHING && opcode == Opcodes.ALOAD) { state = State.SEEN_ALOAD - indexOfLocalVariable = `var` + indexOfLocalVariable = localVariable return } resetState() if (opcode == Opcodes.ALOAD) { state = State.SEEN_ALOAD - indexOfLocalVariable = `var` + indexOfLocalVariable = localVariable return } - mv.visitVarInsn(opcode, `var`) + mv.visitVarInsn(opcode, localVariable) } override fun visitMethodInsn( @@ -159,12 +159,13 @@ class StringMethodsAdapter( val length = newLocal(Type.INT_TYPE) mv.visitVarInsn(Opcodes.ISTORE, length) - constString.forEachIndexed { index, c -> - // if (str.charAt(length - (constString.length() - index)) == c) + // 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, constString.length - index) + 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) @@ -187,9 +188,4 @@ class StringMethodsAdapter( resetState() mv.visitJumpInsn(opcode, label) } - - override fun visitMaxs(maxStack: Int, maxLocals: Int) { - resetState() - mv.visitMaxs(maxStack + 1, maxLocals + 1) - } } \ No newline at end of file From e0e2ffeb3a3e75b39c70e852b76c2f0091dbe864 Mon Sep 17 00:00:00 2001 From: Pavel Egipti Date: Mon, 31 Jul 2023 15:56:27 +0300 Subject: [PATCH 5/6] Add flag to enable bytecode transformation --- .../src/main/kotlin/org/utbot/framework/UtSettings.kt | 9 +++++++++ .../execution/SimpleUtExecutionInstrumentation.kt | 7 +++++-- 2 files changed, 14 insertions(+), 2 deletions(-) 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/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/SimpleUtExecutionInstrumentation.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/SimpleUtExecutionInstrumentation.kt index 03ffd0a607..d86d9e1c83 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 @@ -1,5 +1,6 @@ package org.utbot.instrumentation.instrumentation.execution +import org.utbot.framework.UtSettings import org.utbot.framework.plugin.api.EnvironmentModels import org.utbot.framework.plugin.api.FieldId import org.utbot.framework.plugin.api.UtModel @@ -128,8 +129,10 @@ class SimpleUtExecutionInstrumentation( ): ByteArray { val instrumenter = Instrumenter(classfileBuffer, loader) - instrumenter.visitClass { writer -> - BytecodeTransformer(writer) + if (UtSettings.useBytecodeTransformation) { + instrumenter.visitClass { writer -> + BytecodeTransformer(writer) + } } traceHandler.registerClass(className) From 4a57b7696f1cb3dea14d8f0b365368f7081c5ec6 Mon Sep 17 00:00:00 2001 From: Pavel Egipti Date: Tue, 1 Aug 2023 10:54:54 +0300 Subject: [PATCH 6/6] Fix bug --- .../agent/DynamicClassTransformer.kt | 2 ++ .../SimpleUtExecutionInstrumentation.kt | 4 +-- .../process/InstrumentedProcessMain.kt | 1 + .../instrumentation/rd/InstrumentedProcess.kt | 3 +- .../InstrumentedProcessModel.Generated.kt | 34 +++++++++++-------- .../rd/models/InstrumentedProcessModel.kt | 1 + 6 files changed, 28 insertions(+), 17 deletions(-) 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 d86d9e1c83..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 @@ -1,10 +1,10 @@ package org.utbot.instrumentation.instrumentation.execution -import org.utbot.framework.UtSettings 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 @@ -129,7 +129,7 @@ class SimpleUtExecutionInstrumentation( ): ByteArray { val instrumenter = Instrumenter(classfileBuffer, loader) - if (UtSettings.useBytecodeTransformation) { + if (Agent.dynamicClassTransformer.useBytecodeTransformation) { instrumenter.visitClass { writer -> BytecodeTransformer(writer) } 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 {