Skip to content

Fixes for analysis of reflection and Unsafe #721 #797

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 29, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,4 @@ inline fun <reified R> Field.withAccessibility(block: () -> R): R {
isAccessible = prevAccessibility
setModifiers(this, prevModifiers)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,11 @@ object UtSettings {
*/
var ignoreStaticsFromTrustedLibraries by getBooleanProperty(true)

/**
* Disable sandbox in the concrete executor. All unsafe/dangerous calls will be permitted.
*/
var disableSandbox by getBooleanProperty(false)

override fun toString(): String =
settingsValues
.mapKeys { it.key.name }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -922,7 +922,7 @@ open class FieldId(val declaringClass: ClassId, val name: String) {
return result
}

override fun toString() = safeJField.toString()
override fun toString() = safeJField?.toString() ?: "[unresolved] $declaringClass.$name"
}

inline fun <T> withReflection(block: () -> T): T {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,14 @@ public class Class {
public static boolean desiredAssertionStatus() {
return true;
}

private void checkMemberAccess(SecurityManager sm, int which,
java.lang.Class<?> caller, boolean checkProxyInterfaces) {
// Do nothing to allow everything
}

private void checkPackageAccess(SecurityManager sm, final ClassLoader ccl,
boolean checkProxyInterfaces) {
// Do nothing to allow everything
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.utbot.engine.overrides.security;

import java.security.Permission;

/**
* Overridden implementation for [java.lang.SecurityManager] class
*/
public class UtSecurityManager {
public void checkPermission(Permission perm) {
// Do nothing to allow everything
}

public void checkPackageAccess(String pkg) {
// Do nothing to allow everything
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ private val systemPackages = setOf(
"sun.reflect", // we cannot mock Reflection since mockers are using it during the execution
"java.awt",
"sun.misc",
"jdk.internal",
"kotlin.jvm.internal",
"kotlin.internal"
)
Expand Down
2 changes: 2 additions & 0 deletions utbot-framework/src/main/kotlin/org/utbot/engine/Mocks.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import kotlin.reflect.KFunction2
import kotlin.reflect.KFunction5
import kotlinx.collections.immutable.persistentListOf
import org.utbot.engine.util.mockListeners.MockListenerController
import org.utbot.framework.util.isInaccessibleViaReflection
import soot.BooleanType
import soot.RefType
import soot.Scene
Expand Down Expand Up @@ -183,6 +184,7 @@ class Mocker(
if (isUtMockAssume(mockInfo)) return false // never mock UtMock.assume invocation
if (isUtMockAssumeOrExecuteConcretely(mockInfo)) return false // never mock UtMock.assumeOrExecuteConcretely invocation
if (isOverriddenClass(type)) return false // never mock overriden classes
if (type.isInaccessibleViaReflection) return false // never mock classes that we can't process with reflection
if (isMakeSymbolic(mockInfo)) return true // support for makeSymbolic
if (type.sootClass.isArtificialEntity) return false // never mock artificial types, i.e. Maps$lambda_computeValue_1__7
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import org.utbot.engine.overrides.collections.AssociativeArray
import org.utbot.engine.overrides.collections.RangeModifiableUnlimitedArray
import org.utbot.engine.overrides.collections.UtHashMap
import org.utbot.engine.overrides.collections.UtHashSet
import org.utbot.engine.overrides.security.UtSecurityManager
import org.utbot.engine.overrides.strings.UtNativeString
import org.utbot.engine.overrides.strings.UtString
import org.utbot.engine.overrides.strings.UtStringBuffer
Expand Down Expand Up @@ -86,6 +87,8 @@ val classToWrapper: MutableMap<TypeToBeWrapped, WrapperType> =
putSootClass(java.util.stream.BaseStream::class, UT_STREAM.className)
putSootClass(java.util.stream.Stream::class, UT_STREAM.className)
// TODO primitive streams https://github.com/UnitTestBot/UTBotJava/issues/146

putSootClass(java.lang.SecurityManager::class, UtSecurityManager::class)
}

/**
Expand Down Expand Up @@ -187,6 +190,9 @@ private val wrappers = mapOf(
wrap(java.util.stream.BaseStream::class) { _, addr -> objectValue(STREAM_TYPE, addr, CommonStreamWrapper()) },
wrap(java.util.stream.Stream::class) { _, addr -> objectValue(STREAM_TYPE, addr, CommonStreamWrapper()) },
// TODO primitive streams https://github.com/UnitTestBot/UTBotJava/issues/146

// Security-related wrappers
wrap(SecurityManager::class) { type, addr -> objectValue(type, addr, SecurityManagerWrapper()) },
).also {
// check every `wrapped` class has a corresponding value in [classToWrapper]
it.keys.all { key ->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.utbot.engine

import org.utbot.engine.overrides.security.UtSecurityManager
import org.utbot.framework.plugin.api.UtAssembleModel
import org.utbot.framework.plugin.api.UtModel
import org.utbot.framework.plugin.api.UtStatementModel
import org.utbot.framework.plugin.api.classId
import org.utbot.framework.util.nextModelName
import soot.Scene
import soot.SootClass
import soot.SootMethod

class SecurityManagerWrapper : BaseOverriddenWrapper(utSecurityManagerClass.name) {
private val baseModelName: String = "securityManager"

override fun Traverser.overrideInvoke(
wrapper: ObjectValue,
method: SootMethod,
parameters: List<SymbolicValue>
): List<InvokeResult>? {
// We currently do not overload any [SecurityManager] method symbolically
return null
}

override fun value(resolver: Resolver, wrapper: ObjectValue): UtModel = resolver.run {
val classId = wrapper.type.classId
val addr = holder.concreteAddr(wrapper.addr)
val modelName = nextModelName(baseModelName)

val instantiationChain = mutableListOf<UtStatementModel>()
val modificationChain = mutableListOf<UtStatementModel>()
return UtAssembleModel(addr, classId, modelName, instantiationChain, modificationChain)
}

companion object {
val utSecurityManagerClass: SootClass
get() = Scene.v().getSootClass(UtSecurityManager::class.qualifiedName)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ import org.utbot.framework.plugin.api.util.signature
import org.utbot.framework.plugin.api.util.utContext
import org.utbot.framework.util.executableId
import org.utbot.framework.util.graph
import org.utbot.framework.util.isInaccessibleViaReflection
import java.lang.reflect.ParameterizedType
import kotlin.collections.plus
import kotlin.collections.plusAssign
Expand Down Expand Up @@ -631,7 +632,7 @@ class Traverser(

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

return if (stmt is JAssignStmt) {
return if (stmt is JAssignStmt && stmt.leftOp is JimpleLocal) {
val local = stmt.leftOp as JimpleLocal

localMemoryUpdate(local.variable to symbolicValue)
Expand Down Expand Up @@ -1798,6 +1799,8 @@ class Traverser(
*/
private fun isStaticFieldMeaningful(field: SootField) =
!Modifier.isSynthetic(field.modifiers) &&
// we don't want to set fields that cannot be set via reflection anyway
!field.fieldId.isInaccessibleViaReflection &&
// we don't want to set fields from library classes
!workaround(IGNORE_STATICS_FROM_TRUSTED_LIBRARIES) {
ignoreStaticsFromTrustedLibraries && field.declaringClass.isFromTrustedLibrary()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ import org.utbot.framework.codegen.model.util.equalTo
import org.utbot.framework.codegen.model.util.get
import org.utbot.framework.codegen.model.util.inc
import org.utbot.framework.codegen.model.util.isAccessibleFrom
import org.utbot.framework.codegen.model.util.isInaccessible
import org.utbot.framework.codegen.model.util.length
import org.utbot.framework.codegen.model.util.lessThan
import org.utbot.framework.codegen.model.util.nullLiteral
Expand Down Expand Up @@ -139,6 +138,7 @@ import org.utbot.framework.plugin.api.util.objectClassId
import org.utbot.framework.plugin.api.util.stringClassId
import org.utbot.framework.plugin.api.util.voidClassId
import org.utbot.framework.plugin.api.util.wrapIfPrimitive
import org.utbot.framework.util.isInaccessibleViaReflection
import org.utbot.framework.util.isUnit
import org.utbot.summary.SummarySentenceConstants.TAB
import java.lang.reflect.InvocationTargetException
Expand Down Expand Up @@ -273,7 +273,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
}
}

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

/**
* @return expression for [java.lang.Class] of the given [classId]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,3 @@ fun FieldId.isAccessibleFrom(packageName: String): Boolean {
* Whether or not a field can be set without reflection
*/
fun FieldId.canBeSetIn(packageName: String): Boolean = isAccessibleFrom(packageName) && !isFinal

private val systemClassId = System::class.id

/**
* Security field is inaccessible in Runtime even via reflection.
*/
val FieldId.isInaccessible: Boolean
get() = name == "security" && declaringClass == systemClassId
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import org.utbot.framework.plugin.api.util.singleExecutableId
import org.utbot.framework.plugin.api.util.utContext
import org.utbot.framework.plugin.api.util.withUtContext
import org.utbot.framework.plugin.api.withReflection
import org.utbot.framework.util.isInaccessibleViaReflection
import org.utbot.instrumentation.instrumentation.ArgumentList
import org.utbot.instrumentation.instrumentation.Instrumentation
import org.utbot.instrumentation.instrumentation.InvokeInstrumentation
Expand Down Expand Up @@ -210,15 +211,6 @@ object UtExecutionInstrumentation : Instrumentation<UtConcreteExecutionResult> {
}
}

private val inaccessibleViaReflectionFields = setOf(
"security" to "java.lang.System",
"fieldFilterMap" to "sun.reflect.Reflection",
"methodFilterMap" to "sun.reflect.Reflection"
)

private val FieldId.isInaccessibleViaReflection: Boolean
get() = (name to declaringClass.name) in inaccessibleViaReflectionFields

private fun sortOutException(exception: Throwable): UtExecutionFailure {
if (exception is TimeoutException) {
return UtTimeoutException(exception)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import org.utbot.framework.plugin.api.util.jClass
import org.utbot.framework.plugin.api.util.longClassId
import org.utbot.framework.plugin.api.util.objectClassId
import org.utbot.framework.plugin.api.util.shortClassId
import org.utbot.framework.util.isInaccessibleViaReflection
import org.utbot.framework.util.valueToClassId
import java.lang.reflect.Modifier
import java.util.IdentityHashMap
Expand Down Expand Up @@ -281,7 +282,9 @@ internal class UtModelConstructor(
generateSequence(javaClazz) { it.superclass }.forEach { clazz ->
val allFields = clazz.declaredFields
allFields
.asSequence()
.filter { !(Modifier.isFinal(it.modifiers) && Modifier.isStatic(it.modifiers)) } // TODO: what about static final fields?
.filterNot { it.fieldId.isInaccessibleViaReflection }
.forEach { it.withAccessibility { fields[it.fieldId] = construct(it.get(value), it.type.id) } }
}
return utModel
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.utbot.framework.util

import org.utbot.framework.plugin.api.FieldId
import soot.RefType

/**
* Several fields are inaccessible in runtime even via reflection
*/
val FieldId.isInaccessibleViaReflection: Boolean
get() {
val declaringClassName = declaringClass.name
return declaringClassName in inaccessibleViaReflectionClasses ||
(name to declaringClassName) in inaccessibleViaReflectionFields
}

val RefType.isInaccessibleViaReflection: Boolean
get() {
return className in inaccessibleViaReflectionClasses
}

private val inaccessibleViaReflectionClasses = setOf(
"jdk.internal.reflect.ReflectionFactory",
"jdk.internal.reflect.Reflection",
"jdk.internal.loader.ClassLoaderValue",
"sun.reflect.Reflection",
)

private val inaccessibleViaReflectionFields = setOf(
"security" to "java.lang.System",
)
Original file line number Diff line number Diff line change
Expand Up @@ -2890,3 +2890,16 @@ inline fun <reified T> withUsingReflectionForMaximizingCoverage(maximizeCoverage
UtSettings.maximizeCoverageUsingReflection = prev
}
}

/**
* Run [block] with disabled sandbox in the concrete executor
*/
inline fun <reified T> withoutSandbox(block: () -> T): T {
val prev = UtSettings.disableSandbox
UtSettings.disableSandbox = true
try {
return block()
} finally {
UtSettings.disableSandbox = prev
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import org.utbot.examples.DoNotCalculate
import org.utbot.examples.eq
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.utbot.examples.withoutSandbox
import org.utbot.framework.codegen.CodeGeneration
import org.utbot.framework.plugin.api.CodegenLanguage

internal class JvmCrashExamplesTest : UtValueTestCaseChecker(testClass = JvmCrashExamples::class) {
@Test
Expand All @@ -17,11 +20,23 @@ internal class JvmCrashExamplesTest : UtValueTestCaseChecker(testClass = JvmCras
}

@Test
@Disabled("Java 11 transition")
fun testCrash() {
withoutSandbox {
check(
JvmCrashExamples::crash,
eq(1), // we expect only one execution after minimization
// It seems that we can't calculate coverage when the child JVM has crashed
coverage = DoNotCalculate
)
}
}

@Test
fun testCrashPrivileged() {
check(
JvmCrashExamples::crash,
JvmCrashExamples::crashPrivileged,
eq(1), // we expect only one execution after minimization
// It seems that we can't calculate coverage when the child JVM has crashed
coverage = DoNotCalculate
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ internal class StringExamplesTest : UtValueTestCaseChecker(
)
) {
@Test
@Disabled("Flaky test: https://github.com/UnitTestBot/UTBotJava/issues/131 (will be enabled in new strings PR)")
fun testByteToString() {
// TODO related to the https://github.com/UnitTestBot/UTBotJava/issues/131
withSolverTimeoutInMillis(5000) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.utbot.examples.unsafe

import org.junit.jupiter.api.Test
import org.utbot.examples.DoNotCalculate
import org.utbot.examples.UtValueTestCaseChecker
import org.utbot.examples.eq
import org.utbot.examples.withoutSandbox
import org.utbot.framework.codegen.CodeGeneration
import org.utbot.framework.plugin.api.CodegenLanguage
import org.utbot.framework.plugin.api.MockStrategyApi

internal class UnsafeOperationsTest : UtValueTestCaseChecker(testClass = UnsafeOperations::class) {
@Test
fun checkGetAddressSizeOrZero() {
withoutSandbox {
check(
UnsafeOperations::getAddressSizeOrZero,
eq(1),
{ r -> r!! > 0 },
// Coverage matcher fails (branches: 0/0, instructions: 15/21, lines: 0/0)
// TODO: check coverage calculation: https://github.com/UnitTestBot/UTBotJava/issues/807
coverage = DoNotCalculate
)
}
}

@Test
fun checkGetAddressSizeOrZeroWithMocks() {
withoutSandbox {
check(
UnsafeOperations::getAddressSizeOrZero,
eq(1),
{ r -> r!! > 0 },
// Coverage matcher fails (branches: 0/0, instructions: 15/21, lines: 0/0)
// TODO: check coverage calculation: https://github.com/UnitTestBot/UTBotJava/issues/807
coverage = DoNotCalculate,
mockStrategy = MockStrategyApi.OTHER_CLASSES
)
}
}
}
Loading