Skip to content

Commit 1f812d9

Browse files
authored
Fixes for analysis of reflection and Unsafe #721 (#797)
* Refactored the list of fields that can't be accessed via reflection to use the single rules for all cases (engine, concrete executor, codegen) * Updated this list for Java 11 classes * Added a check to avoid marking reflection-inaccessible static fields as meaningful (they can't be reflexively created by the codegen anyway) * Added `jdk.internal` to the list of system packages to avoid mocking, disabled mocking for classes that can't be accessed via reflection * Added a wrapper for `SecurityManager` and override for two related methods in [Class] * Added UtSettings option to disable sandbox * Temporarily disabled `StringExamplesTest.testByteToString` as flaky
1 parent baf020e commit 1f812d9

File tree

23 files changed

+257
-31
lines changed

23 files changed

+257
-31
lines changed

utbot-core/src/main/kotlin/org/utbot/common/ReflectionUtil.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,4 @@ inline fun <reified R> Field.withAccessibility(block: () -> R): R {
7272
isAccessible = prevAccessibility
7373
setModifiers(this, prevModifiers)
7474
}
75-
}
75+
}

utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,11 @@ object UtSettings {
406406
*/
407407
var ignoreStaticsFromTrustedLibraries by getBooleanProperty(true)
408408

409+
/**
410+
* Disable sandbox in the concrete executor. All unsafe/dangerous calls will be permitted.
411+
*/
412+
var disableSandbox by getBooleanProperty(false)
413+
409414
override fun toString(): String =
410415
settingsValues
411416
.mapKeys { it.key.name }

utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -922,7 +922,7 @@ open class FieldId(val declaringClass: ClassId, val name: String) {
922922
return result
923923
}
924924

925-
override fun toString() = safeJField.toString()
925+
override fun toString() = safeJField?.toString() ?: "[unresolved] $declaringClass.$name"
926926
}
927927

928928
inline fun <T> withReflection(block: () -> T): T {

utbot-framework/src/main/java/org/utbot/engine/overrides/Class.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,14 @@ public class Class {
77
public static boolean desiredAssertionStatus() {
88
return true;
99
}
10+
11+
private void checkMemberAccess(SecurityManager sm, int which,
12+
java.lang.Class<?> caller, boolean checkProxyInterfaces) {
13+
// Do nothing to allow everything
14+
}
15+
16+
private void checkPackageAccess(SecurityManager sm, final ClassLoader ccl,
17+
boolean checkProxyInterfaces) {
18+
// Do nothing to allow everything
19+
}
1020
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.utbot.engine.overrides.security;
2+
3+
import java.security.Permission;
4+
5+
/**
6+
* Overridden implementation for [java.lang.SecurityManager] class
7+
*/
8+
public class UtSecurityManager {
9+
public void checkPermission(Permission perm) {
10+
// Do nothing to allow everything
11+
}
12+
13+
public void checkPackageAccess(String pkg) {
14+
// Do nothing to allow everything
15+
}
16+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ private val systemPackages = setOf(
4545
"sun.reflect", // we cannot mock Reflection since mockers are using it during the execution
4646
"java.awt",
4747
"sun.misc",
48+
"jdk.internal",
4849
"kotlin.jvm.internal",
4950
"kotlin.internal"
5051
)

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import kotlin.reflect.KFunction2
1919
import kotlin.reflect.KFunction5
2020
import kotlinx.collections.immutable.persistentListOf
2121
import org.utbot.engine.util.mockListeners.MockListenerController
22+
import org.utbot.framework.util.isInaccessibleViaReflection
2223
import soot.BooleanType
2324
import soot.RefType
2425
import soot.Scene
@@ -183,6 +184,7 @@ class Mocker(
183184
if (isUtMockAssume(mockInfo)) return false // never mock UtMock.assume invocation
184185
if (isUtMockAssumeOrExecuteConcretely(mockInfo)) return false // never mock UtMock.assumeOrExecuteConcretely invocation
185186
if (isOverriddenClass(type)) return false // never mock overriden classes
187+
if (type.isInaccessibleViaReflection) return false // never mock classes that we can't process with reflection
186188
if (isMakeSymbolic(mockInfo)) return true // support for makeSymbolic
187189
if (type.sootClass.isArtificialEntity) return false // never mock artificial types, i.e. Maps$lambda_computeValue_1__7
188190
if (!isEngineClass(type) && (type.sootClass.isInnerClass || type.sootClass.isLocal || type.sootClass.isAnonymous)) return false // there is no reason (and maybe no possibility) to mock such classes

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import org.utbot.engine.overrides.collections.AssociativeArray
1313
import org.utbot.engine.overrides.collections.RangeModifiableUnlimitedArray
1414
import org.utbot.engine.overrides.collections.UtHashMap
1515
import org.utbot.engine.overrides.collections.UtHashSet
16+
import org.utbot.engine.overrides.security.UtSecurityManager
1617
import org.utbot.engine.overrides.strings.UtNativeString
1718
import org.utbot.engine.overrides.strings.UtString
1819
import org.utbot.engine.overrides.strings.UtStringBuffer
@@ -86,6 +87,8 @@ val classToWrapper: MutableMap<TypeToBeWrapped, WrapperType> =
8687
putSootClass(java.util.stream.BaseStream::class, UT_STREAM.className)
8788
putSootClass(java.util.stream.Stream::class, UT_STREAM.className)
8889
// TODO primitive streams https://github.com/UnitTestBot/UTBotJava/issues/146
90+
91+
putSootClass(java.lang.SecurityManager::class, UtSecurityManager::class)
8992
}
9093

9194
/**
@@ -187,6 +190,9 @@ private val wrappers = mapOf(
187190
wrap(java.util.stream.BaseStream::class) { _, addr -> objectValue(STREAM_TYPE, addr, CommonStreamWrapper()) },
188191
wrap(java.util.stream.Stream::class) { _, addr -> objectValue(STREAM_TYPE, addr, CommonStreamWrapper()) },
189192
// TODO primitive streams https://github.com/UnitTestBot/UTBotJava/issues/146
193+
194+
// Security-related wrappers
195+
wrap(SecurityManager::class) { type, addr -> objectValue(type, addr, SecurityManagerWrapper()) },
190196
).also {
191197
// check every `wrapped` class has a corresponding value in [classToWrapper]
192198
it.keys.all { key ->
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package org.utbot.engine
2+
3+
import org.utbot.engine.overrides.security.UtSecurityManager
4+
import org.utbot.framework.plugin.api.UtAssembleModel
5+
import org.utbot.framework.plugin.api.UtModel
6+
import org.utbot.framework.plugin.api.UtStatementModel
7+
import org.utbot.framework.plugin.api.classId
8+
import org.utbot.framework.util.nextModelName
9+
import soot.Scene
10+
import soot.SootClass
11+
import soot.SootMethod
12+
13+
class SecurityManagerWrapper : BaseOverriddenWrapper(utSecurityManagerClass.name) {
14+
private val baseModelName: String = "securityManager"
15+
16+
override fun Traverser.overrideInvoke(
17+
wrapper: ObjectValue,
18+
method: SootMethod,
19+
parameters: List<SymbolicValue>
20+
): List<InvokeResult>? {
21+
// We currently do not overload any [SecurityManager] method symbolically
22+
return null
23+
}
24+
25+
override fun value(resolver: Resolver, wrapper: ObjectValue): UtModel = resolver.run {
26+
val classId = wrapper.type.classId
27+
val addr = holder.concreteAddr(wrapper.addr)
28+
val modelName = nextModelName(baseModelName)
29+
30+
val instantiationChain = mutableListOf<UtStatementModel>()
31+
val modificationChain = mutableListOf<UtStatementModel>()
32+
return UtAssembleModel(addr, classId, modelName, instantiationChain, modificationChain)
33+
}
34+
35+
companion object {
36+
val utSecurityManagerClass: SootClass
37+
get() = Scene.v().getSootClass(UtSecurityManager::class.qualifiedName)
38+
}
39+
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ import org.utbot.framework.plugin.api.util.signature
9898
import org.utbot.framework.plugin.api.util.utContext
9999
import org.utbot.framework.util.executableId
100100
import org.utbot.framework.util.graph
101+
import org.utbot.framework.util.isInaccessibleViaReflection
101102
import java.lang.reflect.ParameterizedType
102103
import kotlin.collections.plus
103104
import kotlin.collections.plusAssign
@@ -631,7 +632,7 @@ class Traverser(
631632

632633
// so, we have to make an update for the local $r0
633634

634-
return if (stmt is JAssignStmt) {
635+
return if (stmt is JAssignStmt && stmt.leftOp is JimpleLocal) {
635636
val local = stmt.leftOp as JimpleLocal
636637

637638
localMemoryUpdate(local.variable to symbolicValue)
@@ -1798,6 +1799,8 @@ class Traverser(
17981799
*/
17991800
private fun isStaticFieldMeaningful(field: SootField) =
18001801
!Modifier.isSynthetic(field.modifiers) &&
1802+
// we don't want to set fields that cannot be set via reflection anyway
1803+
!field.fieldId.isInaccessibleViaReflection &&
18011804
// we don't want to set fields from library classes
18021805
!workaround(IGNORE_STATICS_FROM_TRUSTED_LIBRARIES) {
18031806
ignoreStaticsFromTrustedLibraries && field.declaringClass.isFromTrustedLibrary()

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ import org.utbot.framework.codegen.model.util.equalTo
7070
import org.utbot.framework.codegen.model.util.get
7171
import org.utbot.framework.codegen.model.util.inc
7272
import org.utbot.framework.codegen.model.util.isAccessibleFrom
73-
import org.utbot.framework.codegen.model.util.isInaccessible
7473
import org.utbot.framework.codegen.model.util.length
7574
import org.utbot.framework.codegen.model.util.lessThan
7675
import org.utbot.framework.codegen.model.util.nullLiteral
@@ -139,6 +138,7 @@ import org.utbot.framework.plugin.api.util.objectClassId
139138
import org.utbot.framework.plugin.api.util.stringClassId
140139
import org.utbot.framework.plugin.api.util.voidClassId
141140
import org.utbot.framework.plugin.api.util.wrapIfPrimitive
141+
import org.utbot.framework.util.isInaccessibleViaReflection
142142
import org.utbot.framework.util.isUnit
143143
import org.utbot.summary.SummarySentenceConstants.TAB
144144
import java.lang.reflect.InvocationTargetException
@@ -273,7 +273,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
273273
}
274274
}
275275

276-
private fun <E> Map<FieldId, E>.accessibleFields(): Map<FieldId, E> = filterKeys { !it.isInaccessible }
276+
private fun <E> Map<FieldId, E>.accessibleFields(): Map<FieldId, E> = filterKeys { !it.isInaccessibleViaReflection }
277277

278278
/**
279279
* @return expression for [java.lang.Class] of the given [classId]

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/FieldIdUtil.kt

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,3 @@ fun FieldId.isAccessibleFrom(packageName: String): Boolean {
2222
* Whether or not a field can be set without reflection
2323
*/
2424
fun FieldId.canBeSetIn(packageName: String): Boolean = isAccessibleFrom(packageName) && !isFinal
25-
26-
private val systemClassId = System::class.id
27-
28-
/**
29-
* Security field is inaccessible in Runtime even via reflection.
30-
*/
31-
val FieldId.isInaccessible: Boolean
32-
get() = name == "security" && declaringClass == systemClassId

utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtExecutionInstrumentation.kt

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import org.utbot.framework.plugin.api.util.singleExecutableId
3030
import org.utbot.framework.plugin.api.util.utContext
3131
import org.utbot.framework.plugin.api.util.withUtContext
3232
import org.utbot.framework.plugin.api.withReflection
33+
import org.utbot.framework.util.isInaccessibleViaReflection
3334
import org.utbot.instrumentation.instrumentation.ArgumentList
3435
import org.utbot.instrumentation.instrumentation.Instrumentation
3536
import org.utbot.instrumentation.instrumentation.InvokeInstrumentation
@@ -210,15 +211,6 @@ object UtExecutionInstrumentation : Instrumentation<UtConcreteExecutionResult> {
210211
}
211212
}
212213

213-
private val inaccessibleViaReflectionFields = setOf(
214-
"security" to "java.lang.System",
215-
"fieldFilterMap" to "sun.reflect.Reflection",
216-
"methodFilterMap" to "sun.reflect.Reflection"
217-
)
218-
219-
private val FieldId.isInaccessibleViaReflection: Boolean
220-
get() = (name to declaringClass.name) in inaccessibleViaReflectionFields
221-
222214
private fun sortOutException(exception: Throwable): UtExecutionFailure {
223215
if (exception is TimeoutException) {
224216
return UtTimeoutException(exception)

utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtModelConstructor.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import org.utbot.framework.plugin.api.util.jClass
2828
import org.utbot.framework.plugin.api.util.longClassId
2929
import org.utbot.framework.plugin.api.util.objectClassId
3030
import org.utbot.framework.plugin.api.util.shortClassId
31+
import org.utbot.framework.util.isInaccessibleViaReflection
3132
import org.utbot.framework.util.valueToClassId
3233
import java.lang.reflect.Modifier
3334
import java.util.IdentityHashMap
@@ -281,7 +282,9 @@ internal class UtModelConstructor(
281282
generateSequence(javaClazz) { it.superclass }.forEach { clazz ->
282283
val allFields = clazz.declaredFields
283284
allFields
285+
.asSequence()
284286
.filter { !(Modifier.isFinal(it.modifiers) && Modifier.isStatic(it.modifiers)) } // TODO: what about static final fields?
287+
.filterNot { it.fieldId.isInaccessibleViaReflection }
285288
.forEach { it.withAccessibility { fields[it.fieldId] = construct(it.get(value), it.type.id) } }
286289
}
287290
return utModel
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package org.utbot.framework.util
2+
3+
import org.utbot.framework.plugin.api.FieldId
4+
import soot.RefType
5+
6+
/**
7+
* Several fields are inaccessible in runtime even via reflection
8+
*/
9+
val FieldId.isInaccessibleViaReflection: Boolean
10+
get() {
11+
val declaringClassName = declaringClass.name
12+
return declaringClassName in inaccessibleViaReflectionClasses ||
13+
(name to declaringClassName) in inaccessibleViaReflectionFields
14+
}
15+
16+
val RefType.isInaccessibleViaReflection: Boolean
17+
get() {
18+
return className in inaccessibleViaReflectionClasses
19+
}
20+
21+
private val inaccessibleViaReflectionClasses = setOf(
22+
"jdk.internal.reflect.ReflectionFactory",
23+
"jdk.internal.reflect.Reflection",
24+
"jdk.internal.loader.ClassLoaderValue",
25+
"sun.reflect.Reflection",
26+
)
27+
28+
private val inaccessibleViaReflectionFields = setOf(
29+
"security" to "java.lang.System",
30+
)

utbot-framework/src/test/kotlin/org/utbot/examples/UtValueTestCaseChecker.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2890,3 +2890,16 @@ inline fun <reified T> withUsingReflectionForMaximizingCoverage(maximizeCoverage
28902890
UtSettings.maximizeCoverageUsingReflection = prev
28912891
}
28922892
}
2893+
2894+
/**
2895+
* Run [block] with disabled sandbox in the concrete executor
2896+
*/
2897+
inline fun <reified T> withoutSandbox(block: () -> T): T {
2898+
val prev = UtSettings.disableSandbox
2899+
UtSettings.disableSandbox = true
2900+
try {
2901+
return block()
2902+
} finally {
2903+
UtSettings.disableSandbox = prev
2904+
}
2905+
}

utbot-framework/src/test/kotlin/org/utbot/examples/exceptions/JvmCrashExamplesTest.kt

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import org.utbot.examples.DoNotCalculate
55
import org.utbot.examples.eq
66
import org.junit.jupiter.api.Disabled
77
import org.junit.jupiter.api.Test
8+
import org.utbot.examples.withoutSandbox
9+
import org.utbot.framework.codegen.CodeGeneration
10+
import org.utbot.framework.plugin.api.CodegenLanguage
811

912
internal class JvmCrashExamplesTest : UtValueTestCaseChecker(testClass = JvmCrashExamples::class) {
1013
@Test
@@ -17,11 +20,23 @@ internal class JvmCrashExamplesTest : UtValueTestCaseChecker(testClass = JvmCras
1720
}
1821

1922
@Test
20-
@Disabled("Java 11 transition")
2123
fun testCrash() {
24+
withoutSandbox {
25+
check(
26+
JvmCrashExamples::crash,
27+
eq(1), // we expect only one execution after minimization
28+
// It seems that we can't calculate coverage when the child JVM has crashed
29+
coverage = DoNotCalculate
30+
)
31+
}
32+
}
33+
34+
@Test
35+
fun testCrashPrivileged() {
2236
check(
23-
JvmCrashExamples::crash,
37+
JvmCrashExamples::crashPrivileged,
2438
eq(1), // we expect only one execution after minimization
39+
// It seems that we can't calculate coverage when the child JVM has crashed
2540
coverage = DoNotCalculate
2641
)
2742
}

utbot-framework/src/test/kotlin/org/utbot/examples/strings/StringExamplesTest.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ internal class StringExamplesTest : UtValueTestCaseChecker(
2626
)
2727
) {
2828
@Test
29+
@Disabled("Flaky test: https://github.com/UnitTestBot/UTBotJava/issues/131 (will be enabled in new strings PR)")
2930
fun testByteToString() {
3031
// TODO related to the https://github.com/UnitTestBot/UTBotJava/issues/131
3132
withSolverTimeoutInMillis(5000) {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.utbot.examples.unsafe
2+
3+
import org.junit.jupiter.api.Test
4+
import org.utbot.examples.DoNotCalculate
5+
import org.utbot.examples.UtValueTestCaseChecker
6+
import org.utbot.examples.eq
7+
import org.utbot.examples.withoutSandbox
8+
import org.utbot.framework.codegen.CodeGeneration
9+
import org.utbot.framework.plugin.api.CodegenLanguage
10+
import org.utbot.framework.plugin.api.MockStrategyApi
11+
12+
internal class UnsafeOperationsTest : UtValueTestCaseChecker(testClass = UnsafeOperations::class) {
13+
@Test
14+
fun checkGetAddressSizeOrZero() {
15+
withoutSandbox {
16+
check(
17+
UnsafeOperations::getAddressSizeOrZero,
18+
eq(1),
19+
{ r -> r!! > 0 },
20+
// Coverage matcher fails (branches: 0/0, instructions: 15/21, lines: 0/0)
21+
// TODO: check coverage calculation: https://github.com/UnitTestBot/UTBotJava/issues/807
22+
coverage = DoNotCalculate
23+
)
24+
}
25+
}
26+
27+
@Test
28+
fun checkGetAddressSizeOrZeroWithMocks() {
29+
withoutSandbox {
30+
check(
31+
UnsafeOperations::getAddressSizeOrZero,
32+
eq(1),
33+
{ r -> r!! > 0 },
34+
// Coverage matcher fails (branches: 0/0, instructions: 15/21, lines: 0/0)
35+
// TODO: check coverage calculation: https://github.com/UnitTestBot/UTBotJava/issues/807
36+
coverage = DoNotCalculate,
37+
mockStrategy = MockStrategyApi.OTHER_CLASSES
38+
)
39+
}
40+
}
41+
}

0 commit comments

Comments
 (0)