From 999536b41254df34bd4800f000a8b13f8399ac8e Mon Sep 17 00:00:00 2001 From: Yury Kamenev Date: Thu, 17 Nov 2022 17:26:27 +0800 Subject: [PATCH 1/3] Added symbolic processing of `newInstance` reflection call --- .github/workflows/framework-tests-matrix.json | 2 +- .../reflection/NewInstanceExampleTest.kt | 16 +++++ .../main/kotlin/org/utbot/engine/Traverser.kt | 65 ++++++++++++++++++- .../org/utbot/engine/types/TypeResolver.kt | 6 +- .../reflection/NewInstanceExample.java | 16 +++++ 5 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 utbot-framework-test/src/test/kotlin/org/utbot/examples/reflection/NewInstanceExampleTest.kt create mode 100644 utbot-sample/src/main/java/org/utbot/examples/reflection/NewInstanceExample.java diff --git a/.github/workflows/framework-tests-matrix.json b/.github/workflows/framework-tests-matrix.json index fadcf414ad..08ad90db4c 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..66eeab6e54 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,10 @@ 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_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 +342,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 +359,7 @@ class Traverser( return when { processStaticInitializerIfRequired(current) -> true unfoldMultiArrayExprIfRequired(current) -> true + pushInitGraphAfterNewInstanceReflectionCall(current) -> true else -> false } } @@ -411,6 +415,50 @@ 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 + val lastStmtRight = lastStmt.rightOp as? JVirtualInvokeExpr ?: return false + val lastMethodInvocation = lastStmtRight.retrieveMethod() + 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 +2963,21 @@ class Traverser( } } + // Return an unbounded symbolic variable for any `forName` overloading + if (instance == null && 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..290e53ee33 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 @@ -344,12 +344,16 @@ internal val CLASS_REF_NUM_DIMENSIONS_DESCRIPTOR: MemoryChunkDescriptor IntType.v() ) +internal val CLASS_REF_SOOT_CLASS = 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; +} From e8d99a8cbe15f6707107289899fdbf6466a92ebf Mon Sep 17 00:00:00 2001 From: Yury Kamenev Date: Mon, 19 Dec 2022 11:53:26 +0800 Subject: [PATCH 2/3] Fixed review issues --- .../src/main/kotlin/org/utbot/engine/Traverser.kt | 13 +++++++++---- .../kotlin/org/utbot/engine/types/TypeResolver.kt | 4 +++- 2 files changed, 12 insertions(+), 5 deletions(-) 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 66eeab6e54..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,6 +88,7 @@ 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 @@ -423,8 +424,11 @@ class Traverser( 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 - val lastStmtRight = lastStmt.rightOp as? JVirtualInvokeExpr ?: return false - val lastMethodInvocation = lastStmtRight.retrieveMethod() + if (!lastStmt.containsInvokeExpr()) { + return false + } + + val lastMethodInvocation = lastStmt.invokeExpr.method if (lastMethodInvocation.subSignature != NEW_INSTANCE_SIGNATURE) { return false } @@ -2963,8 +2967,9 @@ class Traverser( } } - // Return an unbounded symbolic variable for any `forName` overloading - if (instance == null && invocation.method.name == "forName") { + // 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) 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 290e53ee33..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,7 +345,8 @@ internal val CLASS_REF_NUM_DIMENSIONS_DESCRIPTOR: MemoryChunkDescriptor IntType.v() ) -internal val CLASS_REF_SOOT_CLASS = Scene.v().getSootClass(CLASS_REF_CLASSNAME) +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 From 9ab370763ce2b56c371322c6ede4d344c966a31a Mon Sep 17 00:00:00 2001 From: Yury Kamenev Date: Mon, 19 Dec 2022 11:56:57 +0800 Subject: [PATCH 3/3] Added missing quotes --- .github/workflows/framework-tests-matrix.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/framework-tests-matrix.json b/.github/workflows/framework-tests-matrix.json index 08ad90db4c..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 org.utbot.examples.reflection.*\"" + "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",