diff --git a/.github/workflows/framework-tests-matrix.json b/.github/workflows/framework-tests-matrix.json index fadcf414ad..300a6e187c 100644 --- a/.github/workflows/framework-tests-matrix.json +++ b/.github/workflows/framework-tests-matrix.json @@ -22,7 +22,7 @@ }, { "PART_NAME": "examples-part2", - "TESTS_TO_RUN": "--tests \"org.utbot.examples.exceptions.*\" --tests \"org.utbot.examples.invokes.*\" --tests \"org.utbot.examples.lambda.*\" --tests \"org.utbot.examples.make.symbolic.*\" --tests \"org.utbot.examples.math.*\" --tests \"org.utbot.examples.mixed.*\" --tests \"org.utbot.examples.mock.*\" --tests \"org.utbot.examples.models.*\" --tests \"org.utbot.examples.natives.*\" --tests \"org.utbot.examples.objects.*\"" + "TESTS_TO_RUN": "--tests \"org.utbot.examples.exceptions.*\" --tests \"org.utbot.examples.invokes.*\" --tests \"org.utbot.examples.lambda.*\" --tests \"org.utbot.examples.make.symbolic.*\" --tests \"org.utbot.examples.math.*\" --tests \"org.utbot.examples.mixed.*\" --tests \"org.utbot.examples.mock.*\" --tests \"org.utbot.examples.models.*\" --tests \"org.utbot.examples.natives.*\" --tests \"org.utbot.examples.objects.*\" --tests \"org.utbot.examples.reflection.*\"" }, { "PART_NAME": "examples-part3", diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/reflection/NewInstanceExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/reflection/NewInstanceExampleTest.kt new file mode 100644 index 0000000000..813360bf27 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/reflection/NewInstanceExampleTest.kt @@ -0,0 +1,16 @@ +package org.utbot.examples.reflection + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker + +class NewInstanceExampleTest : UtValueTestCaseChecker(NewInstanceExample::class) { + @Test + fun testNewInstanceExample() { + check( + NewInstanceExample::createWithReflectionExample, + eq(1), + { r -> r == 0 } + ) + } +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt index 46d45ee400..dd41bbe450 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -88,8 +88,11 @@ import org.utbot.engine.symbolic.asUpdate import org.utbot.engine.simplificators.MemoryUpdateSimplificator import org.utbot.engine.simplificators.simplifySymbolicStateUpdate import org.utbot.engine.simplificators.simplifySymbolicValue +import org.utbot.engine.types.CLASS_REF_SOOT_CLASS +import org.utbot.engine.types.CLASS_REF_TYPE import org.utbot.engine.types.ENUM_ORDINAL import org.utbot.engine.types.EQUALS_SIGNATURE +import org.utbot.engine.types.NEW_INSTANCE_SIGNATURE import org.utbot.engine.types.HASHCODE_SIGNATURE import org.utbot.engine.types.METHOD_FILTER_MAP_FIELD_SIGNATURE import org.utbot.engine.types.NUMBER_OF_PREFERRED_TYPES @@ -340,7 +343,8 @@ class Traverser( } /** - * Handles preparatory work for static initializers and multi-dimensional arrays creation. + * Handles preparatory work for static initializers, multi-dimensional arrays creation + * and `newInstance` reflection call post-processing. * * For instance, it could push handmade graph with preparation statements to the path selector. * @@ -356,6 +360,7 @@ class Traverser( return when { processStaticInitializerIfRequired(current) -> true unfoldMultiArrayExprIfRequired(current) -> true + pushInitGraphAfterNewInstanceReflectionCall(current) -> true else -> false } } @@ -411,6 +416,53 @@ class Traverser( return true } + /** + * If the previous stms was `newInstance` method invocation, + * pushes a graph of the default constructor of the constructed type, if present, + * and pushes a state with a [InstantiationException] otherwise. + */ + private fun TraversalContext.pushInitGraphAfterNewInstanceReflectionCall(stmt: JAssignStmt): Boolean { + // Check whether the previous stmt was a `newInstance` invocation + val lastStmt = environment.state.path.lastOrNull() as? JAssignStmt ?: return false + if (!lastStmt.containsInvokeExpr()) { + return false + } + + val lastMethodInvocation = lastStmt.invokeExpr.method + if (lastMethodInvocation.subSignature != NEW_INSTANCE_SIGNATURE) { + return false + } + + // Process the current stmt as cast expression + val right = stmt.rightOp as? JCastExpr ?: return false + val castType = right.castType as? RefType ?: return false + val castedJimpleVariable = right.op as? JimpleLocal ?: return false + + val castedLocalVariable = (localVariableMemory.local(castedJimpleVariable.variable) as? ReferenceValue) ?: return false + + val castSootClass = castType.sootClass + + // We need to consider a situation when this class does not have a default constructor + // Since it can be a cast of a class with constructor to the interface (or ot the ancestor without default constructor), + // we cannot always throw a `java.lang.InstantiationException`. + // So, instead we will just continue the analysis without analysis of . + val initMethod = castSootClass.getMethodUnsafe("void ()") ?: return false + + if (!initMethod.canRetrieveBody()) { + return false + } + + val initGraph = ExceptionalUnitGraph(initMethod.activeBody) + + pushToPathSelector( + initGraph, + castedLocalVariable, + callParameters = emptyList(), + ) + + return true + } + /** * Processes static initialization for class. * @@ -2915,6 +2967,22 @@ class Traverser( } } + // Return an unbounded symbolic variable for any overloading of method `forName` of class `java.lang.Class` + // NOTE: we cannot match by a subsignature here since `forName` method has a few overloadings + if (instance == null && invocation.method.declaringClass == CLASS_REF_SOOT_CLASS && invocation.method.name == "forName") { + val forNameResult = unboundedVariable(name = "classForName", invocation.method) + + return OverrideResult(success = true, forNameResult) + } + + // Return an unbounded symbolic variable for the `newInstance` method invocation, + // and at the next traversing step push graph of the resulted type + if (instance?.type == CLASS_REF_TYPE && subSignature == NEW_INSTANCE_SIGNATURE) { + val getInstanceResult = unboundedVariable(name = "newInstance", invocation.method) + + return OverrideResult(success = true, getInstanceResult) + } + val instanceAsWrapperOrNull = instance?.asWrapperOrNull if (instanceAsWrapperOrNull is UtMockWrapper && subSignature == HASHCODE_SIGNATURE) { diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeResolver.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeResolver.kt index 0e58067e2d..84b43861c0 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeResolver.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeResolver.kt @@ -39,6 +39,7 @@ import soot.NullType import soot.PrimType import soot.RefType import soot.Scene +import soot.SootClass import soot.SootField import soot.Type import soot.VoidType @@ -344,12 +345,17 @@ internal val CLASS_REF_NUM_DIMENSIONS_DESCRIPTOR: MemoryChunkDescriptor IntType.v() ) +internal val CLASS_REF_SOOT_CLASS: SootClass + get() = Scene.v().getSootClass(CLASS_REF_CLASSNAME) + internal val OBJECT_TYPE: RefType get() = Scene.v().getSootClass(Object::class.java.canonicalName).type internal val STRING_TYPE: RefType get() = Scene.v().getSootClass(String::class.java.canonicalName).type internal val CLASS_REF_TYPE: RefType - get() = Scene.v().getSootClass(CLASS_REF_CLASSNAME).type + get() = CLASS_REF_SOOT_CLASS.type + +internal val NEW_INSTANCE_SIGNATURE: String = CLASS_REF_SOOT_CLASS.getMethodByName("newInstance").subSignature internal val HASHCODE_SIGNATURE: String = Scene.v() diff --git a/utbot-sample/src/main/java/org/utbot/examples/reflection/NewInstanceExample.java b/utbot-sample/src/main/java/org/utbot/examples/reflection/NewInstanceExample.java new file mode 100644 index 0000000000..21f4c1f6ed --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/reflection/NewInstanceExample.java @@ -0,0 +1,16 @@ +package org.utbot.examples.reflection; + +public class NewInstanceExample { + @SuppressWarnings("deprecation") + int createWithReflectionExample() throws ClassNotFoundException, InstantiationException, IllegalAccessException { + Class cls = Class.forName("org.utbot.examples.reflection.ClassWithDefaultConstructor"); + ClassWithDefaultConstructor classWithDefaultConstructor = (ClassWithDefaultConstructor) cls.newInstance(); + + return classWithDefaultConstructor.x; + } +} + +class ClassWithDefaultConstructor { + + int x; +}