Skip to content

Commit a0d2bef

Browse files
committed
Added symbolic processing of newInstance reflection call
1 parent a3fb966 commit a0d2bef

File tree

5 files changed

+102
-3
lines changed

5 files changed

+102
-3
lines changed

.github/workflows/framework-tests-matrix.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
},
2323
{
2424
"PART_NAME": "examples-part2",
25-
"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.*\""
25+
"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.*\""
2626
},
2727
{
2828
"PART_NAME": "examples-part3",
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.utbot.examples.reflection
2+
3+
import org.junit.jupiter.api.Test
4+
import org.utbot.testcheckers.eq
5+
import org.utbot.testing.UtValueTestCaseChecker
6+
7+
class NewInstanceExampleTest : UtValueTestCaseChecker(NewInstanceExample::class) {
8+
@Test
9+
fun testNewInstanceExample() {
10+
check(
11+
NewInstanceExample::createWithReflectionExample,
12+
eq(1),
13+
{ r -> r == 0 }
14+
)
15+
}
16+
}

utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,10 @@ import org.utbot.engine.symbolic.asUpdate
8888
import org.utbot.engine.simplificators.MemoryUpdateSimplificator
8989
import org.utbot.engine.simplificators.simplifySymbolicStateUpdate
9090
import org.utbot.engine.simplificators.simplifySymbolicValue
91+
import org.utbot.engine.types.CLASS_REF_TYPE
9192
import org.utbot.engine.types.ENUM_ORDINAL
9293
import org.utbot.engine.types.EQUALS_SIGNATURE
94+
import org.utbot.engine.types.NEW_INSTANCE_SIGNATURE
9395
import org.utbot.engine.types.HASHCODE_SIGNATURE
9496
import org.utbot.engine.types.METHOD_FILTER_MAP_FIELD_SIGNATURE
9597
import org.utbot.engine.types.NUMBER_OF_PREFERRED_TYPES
@@ -340,7 +342,8 @@ class Traverser(
340342
}
341343

342344
/**
343-
* Handles preparatory work for static initializers and multi-dimensional arrays creation.
345+
* Handles preparatory work for static initializers, multi-dimensional arrays creation
346+
* and `newInstance` reflection call post-processing.
344347
*
345348
* For instance, it could push handmade graph with preparation statements to the path selector.
346349
*
@@ -356,6 +359,7 @@ class Traverser(
356359
return when {
357360
processStaticInitializerIfRequired(current) -> true
358361
unfoldMultiArrayExprIfRequired(current) -> true
362+
pushInitGraphAfterNewInstanceReflectionCall(current) -> true
359363
else -> false
360364
}
361365
}
@@ -411,6 +415,50 @@ class Traverser(
411415
return true
412416
}
413417

418+
/**
419+
* If the previous stms was `newInstance` method invocation,
420+
* pushes a graph of the default constructor of the constructed type, if present,
421+
* and pushes a state with a [InstantiationException] otherwise.
422+
*/
423+
private fun TraversalContext.pushInitGraphAfterNewInstanceReflectionCall(stmt: JAssignStmt): Boolean {
424+
// Check whether the previous stmt was a `newInstance` invocation
425+
val lastStmt = environment.state.path.lastOrNull() as? JAssignStmt ?: return false
426+
val lastStmtRight = lastStmt.rightOp as? JVirtualInvokeExpr ?: return false
427+
val lastMethodInvocation = lastStmtRight.retrieveMethod()
428+
if (lastMethodInvocation.subSignature != NEW_INSTANCE_SIGNATURE) {
429+
return false
430+
}
431+
432+
// Process the current stmt as cast expression
433+
val right = stmt.rightOp as? JCastExpr ?: return false
434+
val castType = right.castType as? RefType ?: return false
435+
val castedJimpleVariable = right.op as? JimpleLocal ?: return false
436+
437+
val castedLocalVariable = (localVariableMemory.local(castedJimpleVariable.variable) as? ReferenceValue) ?: return false
438+
439+
val castSootClass = castType.sootClass
440+
441+
// We need to consider a situation when this class does not have a default constructor
442+
// Since it can be a cast of a class with constructor to the interface (or ot the ancestor without default constructor),
443+
// we cannot always throw a `java.lang.InstantiationException`.
444+
// So, instead we will just continue the analysis without analysis of <init>.
445+
val initMethod = castSootClass.getMethodUnsafe("void <init>()") ?: return false
446+
447+
if (!initMethod.canRetrieveBody()) {
448+
return false
449+
}
450+
451+
val initGraph = ExceptionalUnitGraph(initMethod.activeBody)
452+
453+
pushToPathSelector(
454+
initGraph,
455+
castedLocalVariable,
456+
callParameters = emptyList(),
457+
)
458+
459+
return true
460+
}
461+
414462
/**
415463
* Processes static initialization for class.
416464
*
@@ -2915,6 +2963,21 @@ class Traverser(
29152963
}
29162964
}
29172965

2966+
// Return an unbounded symbolic variable for any `forName` overloading
2967+
if (instance == null && invocation.method.name == "forName") {
2968+
val forNameResult = unboundedVariable(name = "classForName", invocation.method)
2969+
2970+
return OverrideResult(success = true, forNameResult)
2971+
}
2972+
2973+
// Return an unbounded symbolic variable for the `newInstance` method invocation,
2974+
// and at the next traversing step push <init> graph of the resulted type
2975+
if (instance?.type == CLASS_REF_TYPE && subSignature == NEW_INSTANCE_SIGNATURE) {
2976+
val getInstanceResult = unboundedVariable(name = "newInstance", invocation.method)
2977+
2978+
return OverrideResult(success = true, getInstanceResult)
2979+
}
2980+
29182981
val instanceAsWrapperOrNull = instance?.asWrapperOrNull
29192982

29202983
if (instanceAsWrapperOrNull is UtMockWrapper && subSignature == HASHCODE_SIGNATURE) {

utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeResolver.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,12 +344,16 @@ internal val CLASS_REF_NUM_DIMENSIONS_DESCRIPTOR: MemoryChunkDescriptor
344344
IntType.v()
345345
)
346346

347+
internal val CLASS_REF_SOOT_CLASS = Scene.v().getSootClass(CLASS_REF_CLASSNAME)
348+
347349
internal val OBJECT_TYPE: RefType
348350
get() = Scene.v().getSootClass(Object::class.java.canonicalName).type
349351
internal val STRING_TYPE: RefType
350352
get() = Scene.v().getSootClass(String::class.java.canonicalName).type
351353
internal val CLASS_REF_TYPE: RefType
352-
get() = Scene.v().getSootClass(CLASS_REF_CLASSNAME).type
354+
get() = CLASS_REF_SOOT_CLASS.type
355+
356+
internal val NEW_INSTANCE_SIGNATURE: String = CLASS_REF_SOOT_CLASS.getMethodByName("newInstance").subSignature
353357

354358
internal val HASHCODE_SIGNATURE: String =
355359
Scene.v()
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.utbot.examples.reflection;
2+
3+
public class NewInstanceExample {
4+
@SuppressWarnings("deprecation")
5+
int createWithReflectionExample() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
6+
Class<?> cls = Class.forName("org.utbot.examples.reflection.ClassWithDefaultConstructor");
7+
ClassWithDefaultConstructor classWithDefaultConstructor = (ClassWithDefaultConstructor) cls.newInstance();
8+
9+
return classWithDefaultConstructor.x;
10+
}
11+
}
12+
13+
class ClassWithDefaultConstructor {
14+
15+
int x;
16+
}

0 commit comments

Comments
 (0)