Skip to content
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
2 changes: 1 addition & 1 deletion .github/workflows/framework-tests-matrix.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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 }
)
}
}
70 changes: 69 additions & 1 deletion utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
*
Expand All @@ -356,6 +360,7 @@ class Traverser(
return when {
processStaticInitializerIfRequired(current) -> true
unfoldMultiArrayExprIfRequired(current) -> true
pushInitGraphAfterNewInstanceReflectionCall(current) -> true
else -> false
}
}
Expand Down Expand Up @@ -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
Comment thread
Damtev marked this conversation as resolved.
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 <init>.
val initMethod = castSootClass.getMethodUnsafe("void <init>()") ?: return false

if (!initMethod.canRetrieveBody()) {
return false
}

val initGraph = ExceptionalUnitGraph(initMethod.activeBody)

pushToPathSelector(
initGraph,
castedLocalVariable,
callParameters = emptyList(),
)

return true
}

/**
* Processes static initialization for class.
*
Expand Down Expand Up @@ -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 <init> 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}