Skip to content

Add bytecode transformation for if statement with call of String methods #2446

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -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)
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<DynamicClassTransformer>()
Expand All @@ -19,6 +20,7 @@ private val logger = getLogger<DynamicClassTransformer>()
class DynamicClassTransformer : ClassFileTransformer {
lateinit var transformer: ClassFileTransformer

var useBytecodeTransformation by Delegates.notNull<Boolean>()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use lateinit var here instead of delegate

private val pathsToUserClasses = mutableSetOf<String>()

fun addUserPaths(paths: Iterable<String>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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))

Expand Down
Original file line number Diff line number Diff line change
@@ -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<Result<*>> {
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<Result<*>, BytecodeTransformation> {
override fun create(): BytecodeTransformation = BytecodeTransformation()
}
}
Original file line number Diff line number Diff line change
@@ -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<out String>?
): MethodVisitor {
val methodVisitor = cv.visitMethod(access, name, descriptor, signature, exceptions)
return StringMethodsAdapter(api, access, descriptor, methodVisitor)
}
}
Loading