Skip to content

Commit 4eb9a87

Browse files
Damtevdenis-fokin
authored andcommitted
Added symbolic processing of newInstance reflection call (#1540)
(cherry picked from commit ac31b7f)
1 parent 635edb5 commit 4eb9a87

File tree

5 files changed

+109
-3
lines changed

5 files changed

+109
-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: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,11 @@ 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_SOOT_CLASS
92+
import org.utbot.engine.types.CLASS_REF_TYPE
9193
import org.utbot.engine.types.ENUM_ORDINAL
9294
import org.utbot.engine.types.EQUALS_SIGNATURE
95+
import org.utbot.engine.types.NEW_INSTANCE_SIGNATURE
9396
import org.utbot.engine.types.HASHCODE_SIGNATURE
9497
import org.utbot.engine.types.METHOD_FILTER_MAP_FIELD_SIGNATURE
9598
import org.utbot.engine.types.NUMBER_OF_PREFERRED_TYPES
@@ -340,7 +343,8 @@ class Traverser(
340343
}
341344

342345
/**
343-
* Handles preparatory work for static initializers and multi-dimensional arrays creation.
346+
* Handles preparatory work for static initializers, multi-dimensional arrays creation
347+
* and `newInstance` reflection call post-processing.
344348
*
345349
* For instance, it could push handmade graph with preparation statements to the path selector.
346350
*
@@ -356,6 +360,7 @@ class Traverser(
356360
return when {
357361
processStaticInitializerIfRequired(current) -> true
358362
unfoldMultiArrayExprIfRequired(current) -> true
363+
pushInitGraphAfterNewInstanceReflectionCall(current) -> true
359364
else -> false
360365
}
361366
}
@@ -411,6 +416,53 @@ class Traverser(
411416
return true
412417
}
413418

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

2970+
// Return an unbounded symbolic variable for any overloading of method `forName` of class `java.lang.Class`
2971+
// NOTE: we cannot match by a subsignature here since `forName` method has a few overloadings
2972+
if (instance == null && invocation.method.declaringClass == CLASS_REF_SOOT_CLASS && invocation.method.name == "forName") {
2973+
val forNameResult = unboundedVariable(name = "classForName", invocation.method)
2974+
2975+
return OverrideResult(success = true, forNameResult)
2976+
}
2977+
2978+
// Return an unbounded symbolic variable for the `newInstance` method invocation,
2979+
// and at the next traversing step push <init> graph of the resulted type
2980+
if (instance?.type == CLASS_REF_TYPE && subSignature == NEW_INSTANCE_SIGNATURE) {
2981+
val getInstanceResult = unboundedVariable(name = "newInstance", invocation.method)
2982+
2983+
return OverrideResult(success = true, getInstanceResult)
2984+
}
2985+
29182986
val instanceAsWrapperOrNull = instance?.asWrapperOrNull
29192987

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

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import soot.NullType
3939
import soot.PrimType
4040
import soot.RefType
4141
import soot.Scene
42+
import soot.SootClass
4243
import soot.SootField
4344
import soot.Type
4445
import soot.VoidType
@@ -344,12 +345,17 @@ internal val CLASS_REF_NUM_DIMENSIONS_DESCRIPTOR: MemoryChunkDescriptor
344345
IntType.v()
345346
)
346347

348+
internal val CLASS_REF_SOOT_CLASS: SootClass
349+
get() = Scene.v().getSootClass(CLASS_REF_CLASSNAME)
350+
347351
internal val OBJECT_TYPE: RefType
348352
get() = Scene.v().getSootClass(Object::class.java.canonicalName).type
349353
internal val STRING_TYPE: RefType
350354
get() = Scene.v().getSootClass(String::class.java.canonicalName).type
351355
internal val CLASS_REF_TYPE: RefType
352-
get() = Scene.v().getSootClass(CLASS_REF_CLASSNAME).type
356+
get() = CLASS_REF_SOOT_CLASS.type
357+
358+
internal val NEW_INSTANCE_SIGNATURE: String = CLASS_REF_SOOT_CLASS.getMethodByName("newInstance").subSignature
353359

354360
internal val HASHCODE_SIGNATURE: String =
355361
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)