Skip to content

Commit

Permalink
Executor module updated to actual usvm with some improvements and fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
DaniilStepanov committed Oct 9, 2023
1 parent 555da94 commit aad21bb
Show file tree
Hide file tree
Showing 19 changed files with 225 additions and 96 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ class WorkerClassLoader(
}
}
classesToReinit.forEach { cl ->
cl.declaredMethods.find { it.name == "generatedClinit0" }?.invoke(null)
try {
cl.declaredMethods.find { it.name == "generatedClinit0" }?.invokeWithAccessibility(null, listOf())
} catch (e: Throwable) {
//cannot access some classes, for example, enums
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import org.usvm.instrumentation.testcase.api.UTestExecutionTimedOutResult
import org.usvm.instrumentation.testcase.descriptor.UTestExceptionDescriptor
import org.usvm.instrumentation.util.InstrumentationModuleConstants
import org.usvm.instrumentation.util.UTestExecutorInitException
import java.util.concurrent.CancellationException
import java.util.concurrent.TimeoutException
import kotlin.reflect.KClass
import kotlin.time.Duration
Expand Down Expand Up @@ -103,6 +104,14 @@ class UTestConcreteExecutor(
raisedByUserCode = false
)
UTestExecutionFailedResult(descriptor)
} catch (e: CancellationException) {
val descriptor = UTestExceptionDescriptor(
type = jcClasspath.findClass<Exception>().toType(),
message = "CancellationException",
stackTrace = listOf(),
raisedByUserCode = false
)
UTestExecutionFailedResult(descriptor)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import org.usvm.instrumentation.testcase.descriptor.Value2DescriptorConverter
import org.usvm.instrumentation.testcase.executor.UTestExpressionExecutor
import org.usvm.instrumentation.util.InstrumentationModuleConstants
import org.usvm.instrumentation.util.URLClassPathLoader
import java.io.File
import java.lang.Exception

class UTestExecutor(
Expand All @@ -42,16 +43,23 @@ class UTestExecutor(
jcClasspath = jcClasspath
)

private fun reset() {
initStateDescriptorBuilder = Value2DescriptorConverter(workerClassLoader, null)
staticDescriptorsBuilder = StaticDescriptorsBuilder(workerClassLoader, initStateDescriptorBuilder)
JcInstructionTracer.reset()
MockCollector.mocks.clear()
}

fun executeUTest(uTest: UTest): UTestExecutionResult {
when (InstrumentationModuleConstants.testExecutorStaticsRollbackStrategy) {
StaticsRollbackStrategy.HARD -> workerClassLoader = createWorkerClassLoader()
else -> {}
}
JcInstructionTracer.reset()
MockCollector.mocks.clear()
reset()

val accessedStatics = mutableSetOf<Pair<JcField, JcInstructionTracer.StaticFieldAccessType>>()
val callMethodExpr = uTest.callMethodExpression

val executor = UTestExpressionExecutor(workerClassLoader, accessedStatics, mockHelper)
executor.executeUTestInsts(uTest.initStatements)
?.onFailure {
Expand All @@ -68,6 +76,7 @@ class UTestExecutor(
accessedStatics = hashSetOf()
)

executor.clearCache()
executor.executeUTestInsts(uTest.initStatements)
?.onFailure {
return UTestExecutionInitFailedResult(
Expand Down Expand Up @@ -99,7 +108,7 @@ class UTestExecutor(
)
}
val methodInvocationResultDescriptor =
resultStateDescriptorBuilder.buildDescriptorResultFromAny(unpackedInvocationResult).getOrNull()
resultStateDescriptorBuilder.buildDescriptorResultFromAny(unpackedInvocationResult, callMethodExpr.type).getOrNull()
val trace = JcInstructionTracer.getTrace()
accessedStatics.addAll(trace.statics.toSet())
val resultExecutionState =
Expand All @@ -114,6 +123,7 @@ class UTestExecutor(
workerClassLoader.reset(accessedStaticsFields)
}


return UTestExecutionSuccessResult(
trace.trace, methodInvocationResultDescriptor, initExecutionState, resultExecutionState
)
Expand All @@ -124,7 +134,7 @@ class UTestExecutor(
exception: Throwable,
raisedByUserCode: Boolean
): UTestExceptionDescriptor {
val descriptor = builder.buildDescriptorResultFromAny(exception).getOrNull() as? UTestExceptionDescriptor
val descriptor = builder.buildDescriptorResultFromAny(exception, null).getOrNull() as? UTestExceptionDescriptor
return descriptor
?.also { it.raisedByUserCode = raisedByUserCode }
?: UTestExceptionDescriptor(
Expand All @@ -147,10 +157,10 @@ class UTestExecutor(
val argsDescriptors = callMethodExpr.args.map {
buildDescriptorFromUTestExpr(it, executor)?.getOrNull()
}
executor.clearCache()
val isInit = descriptorBuilder.previousState == null
val statics = if (isInit) {
staticDescriptorsBuilder.builtInitialDescriptors.mapValues { it.value!! }
staticDescriptorsBuilder.builtInitialDescriptors
.mapValues { it.value!! }
} else {
staticDescriptorsBuilder.buildDescriptorsForExecutedStatics(accessedStatics, descriptorBuilder).getOrThrow()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,13 @@ fun AbstractBuffer.readJcField(jcClasspath: JcClasspath): JcField {
}

fun AbstractBuffer.readJcType(jcClasspath: JcClasspath): JcType? {
val typeName = readString()
var typeName = readString()
if (typeName == "type_is_null") return null
return jcClasspath.findTypeOrNull(typeName)
jcClasspath.findTypeOrNull(typeName)?.let { return it }
//We need this because of jacodb peculiarity with typenames...
while (typeName.contains(".")) {
typeName = typeName.reversed().replaceFirst('.', '$').reversed()
jcClasspath.findTypeOrNull(typeName)?.let { return it }
}
return null
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class Descriptor2ValueConverter(private val workerClassLoader: ClassLoader) {
is UTestConstantDescriptor.Long -> descriptor.value
is UTestConstantDescriptor.Null -> null
is UTestConstantDescriptor.Short -> descriptor.value
is UTestConstantDescriptor.String -> descriptor.value
is UTestConstantDescriptor.String -> string(descriptor)
is UTestCyclicReferenceDescriptor -> {
descriptorToObject
.toList()
Expand All @@ -47,6 +47,13 @@ class Descriptor2ValueConverter(private val workerClassLoader: ClassLoader) {
is UTestExceptionDescriptor -> `exception`(descriptor)
}

private fun string(descriptor: UTestConstantDescriptor.String) =
if (descriptor.value == InstrumentationModuleConstants.nameForExistingButNullString) {
ReflectionUtils.UNSAFE.allocateInstance(String::class.java) as String
} else {
descriptor.value
}

private fun array(descriptor: UTestArrayDescriptor.Array): Any {
val jcElementType = descriptor.elementType
val cp = descriptor.elementType.classpath
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ import org.jacodb.api.ext.enumValues
import org.jacodb.api.ext.isEnum
import org.usvm.instrumentation.classloader.WorkerClassLoader
import org.usvm.instrumentation.instrumentation.JcInstructionTracer.StaticFieldAccessType
import org.usvm.instrumentation.util.allDeclaredFields
import org.usvm.instrumentation.util.getFieldValue
import org.usvm.instrumentation.util.setFieldValue
import org.usvm.instrumentation.util.toJavaField
import org.usvm.instrumentation.util.*

class StaticDescriptorsBuilder(
private var workerClassLoader: WorkerClassLoader,
Expand Down Expand Up @@ -57,7 +54,8 @@ class StaticDescriptorsBuilder(
private fun buildDescriptor(jcField: JcField, descriptorBuilder: Value2DescriptorConverter): UTestValueDescriptor? {
val jField = jcField.toJavaField(workerClassLoader) ?: return null
val jFieldValue = jField.getFieldValue(null)
val jFieldValueDescriptor = descriptorBuilder.buildDescriptorResultFromAny(jFieldValue)
val cp = jcField.enclosingClass.classpath
val jFieldValueDescriptor = descriptorBuilder.buildDescriptorResultFromAny(jFieldValue, jcField.type.toJcType(cp))
return jFieldValueDescriptor.getOrNull()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
package org.usvm.instrumentation.testcase.descriptor

import org.usvm.instrumentation.util.getFieldValue
import org.jacodb.api.JcClassOrInterface
import org.jacodb.api.JcField
import org.jacodb.api.JcType
import org.jacodb.api.ext.*
import org.usvm.instrumentation.classloader.WorkerClassLoader
import org.usvm.instrumentation.util.stringType
import org.usvm.instrumentation.util.toJavaField
import org.usvm.instrumentation.testcase.executor.UTestExpressionExecutor
import org.usvm.instrumentation.testcase.api.UTestExpression
import org.usvm.instrumentation.util.allDeclaredFields
import org.usvm.instrumentation.util.toJcType
import org.usvm.instrumentation.util.*
import java.util.*

open class Value2DescriptorConverter(
Expand All @@ -38,20 +33,20 @@ open class Value2DescriptorConverter(
testExecutor: UTestExpressionExecutor,
): Result<UTestValueDescriptor>? {
testExecutor.executeUTestInst(uTestExpression)
.onSuccess { return buildDescriptorResultFromAny(it) }
.onSuccess { return buildDescriptorResultFromAny(it, uTestExpression.type) }
.onFailure { return Result.failure(it) }
return null
}

fun buildDescriptorResultFromAny(any: Any?, depth: Int = 0): Result<UTestValueDescriptor> =
fun buildDescriptorResultFromAny(any: Any?, type: JcType?, depth: Int = 0): Result<UTestValueDescriptor> =
try {
Result.success(buildDescriptorFromAny(any, depth))
Result.success(buildDescriptorFromAny(any, type, depth))
} catch (e: Throwable) {
Result.failure(e)
}

private fun buildDescriptorFromAny(any: Any?, depth: Int = 0): UTestValueDescriptor {
val builtDescriptor = buildDescriptor(any, depth)
private fun buildDescriptorFromAny(any: Any?, type: JcType?, depth: Int = 0): UTestValueDescriptor {
val builtDescriptor = buildDescriptor(any, type, depth)
val descriptorFromPreviousState = previousState?.objectToDescriptor?.get(any) ?: return builtDescriptor
if (builtDescriptor.structurallyEqual(descriptorFromPreviousState)) {
objectToDescriptor[any] = descriptorFromPreviousState
Expand All @@ -60,8 +55,8 @@ open class Value2DescriptorConverter(
return builtDescriptor
}

private fun buildDescriptor(any: Any?, depth: Int = 0): UTestValueDescriptor {
if (any == null) return `null`(jcClasspath.nullType)
private fun buildDescriptor(any: Any?, type: JcType?, depth: Int = 0): UTestValueDescriptor {
if (any == null) return `null`(type ?: jcClasspath.nullType)
return objectToDescriptor.getOrPut(any) {
when (any) {
is Boolean -> const(any)
Expand All @@ -82,6 +77,7 @@ open class Value2DescriptorConverter(
is FloatArray -> array(any, depth + 1)
is DoubleArray -> array(any, depth + 1)
is Array<*> -> array(any, depth + 1)
is Enum<*> -> `enum`(any, depth + 1)
is Class<*> -> `class`(any)
is Throwable -> `exception`(any, depth + 1)
else -> `object`(any, depth + 1)
Expand All @@ -95,7 +91,12 @@ open class Value2DescriptorConverter(

private fun const(value: Char) = UTestConstantDescriptor.Char(value, jcClasspath.char)

private fun const(value: String) = UTestConstantDescriptor.String(value, jcClasspath.stringType())
private fun const(value: String) =
try {
UTestConstantDescriptor.String(value, jcClasspath.stringType()).also { value.length }
} catch (e: Throwable) {
UTestConstantDescriptor.String(InstrumentationModuleConstants.nameForExistingButNullString, jcClasspath.stringType())
}

private fun const(number: Number) = when (number) {
is Byte -> UTestConstantDescriptor.Byte(number, jcClasspath.byte)
Expand Down Expand Up @@ -127,7 +128,7 @@ open class Value2DescriptorConverter(
)
createCyclicRef(descriptor, array) {
for (i in array.indices) {
listOfRefs.add(buildDescriptorFromAny(array[i], depth))
listOfRefs.add(buildDescriptorFromAny(array[i], elementType, depth))
}
}
}
Expand Down Expand Up @@ -156,18 +157,17 @@ open class Value2DescriptorConverter(

private fun `object`(value: Any, depth: Int): UTestValueDescriptor {
val jcClass = jcClasspath.findClass(value::class.java.name)
if (jcClass.isEnum) return `enum`(jcClass, value, depth)
val jcType = jcClass.toType()
val fields = mutableMapOf<JcField, UTestValueDescriptor>()
val uTestObjectDescriptor = UTestObjectDescriptor(jcType, fields, System.identityHashCode(value))
return createCyclicRef(uTestObjectDescriptor, value) {
jcClass.allDeclaredFields
//TODO! Decide for which fields descriptors should be build
.filterNot { it.isFinal || it.isTransient }
.filterNot { it.isTransient }
.forEach { jcField ->
val jField = jcField.toJavaField(classLoader) ?: return@forEach
val fieldValue = jField.getFieldValue(value)
val fieldDescriptor = buildDescriptorFromAny(fieldValue, depth)
val fieldDescriptor = buildDescriptorFromAny(fieldValue, jcField.type.toJcType(jcClasspath), depth)
fields[jcField] = fieldDescriptor
}
}
Expand All @@ -176,7 +176,7 @@ open class Value2DescriptorConverter(
private fun `exception`(exception: Throwable, depth: Int): UTestExceptionDescriptor {
val jcClass = jcClasspath.findClass(exception::class.java.name)
val jcType = jcClass.toType()
val stackTraceElementDescriptors = exception.stackTrace.map { buildDescriptorFromAny(it, depth) }
val stackTraceElementDescriptors = exception.stackTrace.map { buildDescriptorFromAny(it, jcType, depth) }
return UTestExceptionDescriptor(
jcType,
exception.message ?: "",
Expand All @@ -185,7 +185,9 @@ open class Value2DescriptorConverter(
)
}

private fun `enum`(jcClass: JcClassOrInterface, value: Any, depth: Int): UTestEnumValueDescriptor {
private fun `enum`(/*jcClass: JcClassOrInterface, */value: Any, depth: Int): UTestEnumValueDescriptor {
val enumValueJcClass = jcClasspath.findClass(value::class.java.name)
val jcClass = if (!enumValueJcClass.isEnum) enumValueJcClass.superClass!! else enumValueJcClass
val fields = mutableMapOf<JcField, UTestValueDescriptor>()
val enumValueName = value.toString()
val jcType = jcClass.toType()
Expand All @@ -196,7 +198,7 @@ open class Value2DescriptorConverter(
.forEach { jcField ->
val jField = jcField.toJavaField(classLoader) ?: return@forEach
val fieldValue = jField.getFieldValue(value)
val fieldDescriptor = buildDescriptorFromAny(fieldValue, depth)
val fieldDescriptor = buildDescriptorFromAny(fieldValue, jcField.type.toJcType(jcClasspath), depth)
fields[jcField] = fieldDescriptor
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ object InstrumentationModuleConstants {
//Rollback strategy
val testExecutorStaticsRollbackStrategy = StaticsRollbackStrategy.REINIT

const val nameForExistingButNullString = "USVM_GENERATED_NULL_STRING"

//Passes as environment parameter
val pathToUsvmInstrumentationJar: String
get() = System.getenv("usvm-jvm-instrumentation-jar")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,11 +181,6 @@ fun TypeName.isPrimitiveArray() =

fun JcMethod.toJavaMethod(classLoader: ClassLoader): Method {
val klass = Class.forName(enclosingClass.name, false, classLoader)
try {
klass.declaredMethods
} catch (e: Throwable) {
e.printStackTrace()
}
return klass.declaredMethods.find { it.isSameSignatures(this) }
?: throw TestExecutorException("Can't find method in classpath")
}
Expand Down
Loading

0 comments on commit aad21bb

Please sign in to comment.