Skip to content

Fix the way of setting static final fields #466

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
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: 2 additions & 0 deletions utbot-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ apply from: "${parent.projectDir}/gradle/include/jvm-project.gradle"
dependencies {
implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlin_logging_version
implementation group: 'net.java.dev.jna', name: 'jna-platform', version: '5.5.0'

testImplementation group: 'junit', name: 'junit', version: junit4_version
}

shadowJar {
Expand Down
42 changes: 28 additions & 14 deletions utbot-core/src/main/kotlin/org/utbot/common/ReflectionUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import java.lang.reflect.AccessibleObject
import java.lang.reflect.Field
import java.lang.reflect.Modifier
import sun.misc.Unsafe
import java.lang.reflect.Method

object Reflection {
val unsafe: Unsafe
Expand All @@ -15,15 +16,18 @@ object Reflection {
unsafe = f.get(null) as Unsafe
}

private val getDeclaredFields0Method: Method =
Class::class.java.getDeclaredMethod("getDeclaredFields0", Boolean::class.java).apply {
isAccessible = true
}

@Suppress("UNCHECKED_CAST")
private val fields: Array<Field> =
getDeclaredFields0Method.invoke(Field::class.java, false) as Array<Field>

// TODO: works on JDK 8-17. Doesn't work on JDK 18
private val modifiersField: Field = run {
val getDeclaredFields0 = Class::class.java.getDeclaredMethod("getDeclaredFields0", Boolean::class.java)
getDeclaredFields0.isAccessible = true
@Suppress("UNCHECKED_CAST")
val fields = getDeclaredFields0.invoke(Field::class.java, false) as Array<Field>
private val modifiersField: Field =
fields.first { it.name == "modifiers" }
}

init {
modifiersField.isAccessible = true
Expand All @@ -36,8 +40,9 @@ object Reflection {

inline fun <R> AccessibleObject.withAccessibility(block: () -> R): R {
val prevAccessibility = isAccessible
isAccessible = true

try {
isAccessible = true
return block()
} finally {
isAccessible = prevAccessibility
Expand All @@ -47,15 +52,24 @@ inline fun <R> AccessibleObject.withAccessibility(block: () -> R): R {
/**
* Executes given [block] with removed final modifier and restores it back after execution.
* Also sets `isAccessible` to `true` and restores it back.
*
* Please note, that this function doesn't guarantee that reflection calls in the [block] always succeed. The problem is
* that prior calls to reflection may result in caching internal FieldAccessor field which is not suitable for setting
* [this]. But if you didn't call reflection previously, this function should help.
*
* Also note, that primitive static final fields may be inlined, so may not be possible to change.
*/
inline fun<reified R> Field.withRemovedFinalModifier(block: () -> R): R {
inline fun <reified R> Field.withAccessibility(block: () -> R): R {
val prevModifiers = modifiers
val prevAccessibility = isAccessible

isAccessible = true
setModifiers(this, modifiers and Modifier.FINAL.inv())
this.withAccessibility {
try {
return block()
} finally {
setModifiers(this, prevModifiers)
}

try {
return block()
} finally {
isAccessible = prevAccessibility
setModifiers(this, prevModifiers)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.utbot.examples.reflection;

public class ClassWithDifferentModifiers {
@SuppressWarnings({"All"})
private int privateField;

private static final Wrapper privateStaticFinalField = new Wrapper(1);

public ClassWithDifferentModifiers() {
privateField = 0;
}

int packagePrivateMethod() {
return 1;
}

private int privateMethod() {
return 1;
}

public static class Wrapper {
public int x;
public Wrapper(int x) {
this.x = x;
}
}
}
117 changes: 117 additions & 0 deletions utbot-core/src/test/kotlin/org/utbot/common/ReflectionUtilTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package org.utbot.common

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Test
import org.utbot.examples.reflection.ClassWithDifferentModifiers
import org.utbot.examples.reflection.ClassWithDifferentModifiers.Wrapper

class ReflectionUtilTest {
private val testedClass = ClassWithDifferentModifiers::class.java

@Test
fun testPackagePrivateInvoke() {
val method = testedClass.declaredMethods.first { it.name == "packagePrivateMethod"}
val instance = ClassWithDifferentModifiers()

method.apply {
withAccessibility {
assertEquals(1, invoke(instance))
}
}
}

@Test
fun testPrivateInvoke() {
val method = testedClass.declaredMethods.first { it.name == "privateMethod"}
val instance = ClassWithDifferentModifiers()

method.apply {
withAccessibility {
assertEquals(1, invoke(instance))
}
}

}

@Test
fun testPrivateFieldSetting() {
val field = testedClass.declaredFields.first { it.name == "privateField" }
val instance = ClassWithDifferentModifiers()

field.apply {
withAccessibility {
set(instance, 0)
}
}
}

@Test
fun testPrivateFieldGetting() {
val field = testedClass.declaredFields.first { it.name == "privateField" }
val instance = ClassWithDifferentModifiers()

field.apply {
withAccessibility {
assertEquals(0, get(instance))
}
}

}

@Test
fun testPrivateFieldGettingAfterSetting() {
val field = testedClass.declaredFields.first { it.name == "privateField" }
val instance = ClassWithDifferentModifiers()


field.apply {
withAccessibility {
set(instance, 1)
}

withAccessibility {
assertEquals(1, get(instance))
}
}
}

@Test
fun testPrivateStaticFinalFieldSetting() {
val field = testedClass.declaredFields.first { it.name == "privateStaticFinalField" }

field.apply {
withAccessibility {
set(null, Wrapper(2))
}
}
}

@Test
fun testPrivateStaticFinalFieldGetting() {
val field = testedClass.declaredFields.first { it.name == "privateStaticFinalField" }

field.apply {
withAccessibility {
val value = get(null) as? Wrapper
assertNotNull(value)
}
}
}

@Test
fun testPrivateStaticFinalFieldGettingAfterSetting() {
val field = testedClass.declaredFields.first { it.name == "privateStaticFinalField" }

field.apply {
withAccessibility {
set(null, Wrapper(3))
}

withAccessibility {
val value = (get(null) as? Wrapper)?.x
assertEquals(3, value)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.utbot.framework.concrete

import org.utbot.common.withRemovedFinalModifier
import org.utbot.common.withAccessibility
import org.utbot.framework.plugin.api.util.signature
import org.utbot.instrumentation.instrumentation.mock.MockConfig
import java.lang.reflect.Field
Expand Down Expand Up @@ -36,7 +36,7 @@ class MethodMockController(
isMockField = clazz.declaredFields.firstOrNull { it.name == MockConfig.IS_MOCK_FIELD + id }
?: error("No field ${MockConfig.IS_MOCK_FIELD + id} in $clazz")

isMockField.withRemovedFinalModifier {
isMockField.withAccessibility {
isMockField.set(instance, true)
}

Expand All @@ -46,7 +46,7 @@ class MethodMockController(
}

override fun close() {
isMockField.withRemovedFinalModifier {
isMockField.withAccessibility {
isMockField.set(instance, false)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package org.utbot.framework.concrete
import org.utbot.common.StopWatch
import org.utbot.common.ThreadBasedExecutor
import org.utbot.common.withAccessibility
import org.utbot.common.withRemovedFinalModifier
import org.utbot.framework.UtSettings
import org.utbot.framework.assemble.AssembleModelGenerator
import org.utbot.framework.plugin.api.Coverage
Expand Down Expand Up @@ -260,7 +259,7 @@ object UtExecutionInstrumentation : Instrumentation<UtConcreteExecutionResult> {
try {
staticFields.forEach { (fieldId, value) ->
fieldId.field.run {
withRemovedFinalModifier {
withAccessibility {
savedFields[fieldId] = get(null)
set(null, value)
}
Expand All @@ -270,7 +269,7 @@ object UtExecutionInstrumentation : Instrumentation<UtConcreteExecutionResult> {
} finally {
savedFields.forEach { (fieldId, value) ->
fieldId.field.run {
withRemovedFinalModifier {
withAccessibility {
set(null, value)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.utbot.instrumentation.instrumentation

import org.utbot.common.withRemovedFinalModifier
import org.utbot.common.withAccessibility
import org.utbot.framework.plugin.api.util.field
import org.utbot.instrumentation.util.StaticEnvironment
import java.lang.reflect.Field
Expand Down Expand Up @@ -47,7 +47,7 @@ class InvokeWithStaticsInstrumentation : Instrumentation<Result<*>> {
staticEnvironment?.run {
listOfFields.forEach { (fieldId, value) ->
fieldId.field.run {
withRemovedFinalModifier {
withAccessibility {
set(null, value)
}
}
Expand Down Expand Up @@ -75,14 +75,14 @@ class InvokeWithStaticsInstrumentation : Instrumentation<Result<*>> {
init {
val staticFields = clazz.declaredFields
.filter { checkField(it) } // TODO: think on this
.associate { it.name to it.withRemovedFinalModifier { it.get(null) } }
.associate { it.name to it.withAccessibility { it.get(null) } }
savedFields = staticFields
}

fun restore() {
clazz.declaredFields
.filter { checkField(it) }
.forEach { it.withRemovedFinalModifier { it.set(null, savedFields[it.name]) } }
.forEach { it.withAccessibility { it.set(null, savedFields[it.name]) } }
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.utbot.instrumentation.instrumentation.coverage

import org.utbot.common.withRemovedFinalModifier
import org.utbot.common.withAccessibility
import org.utbot.instrumentation.ConcreteExecutor
import org.utbot.instrumentation.Settings
import org.utbot.instrumentation.instrumentation.ArgumentList
Expand Down Expand Up @@ -43,7 +43,7 @@ object CoverageInstrumentation : Instrumentation<Result<*>> {
val visitedLinesField = clazz.fields.firstOrNull { it.name == probesFieldName }
?: throw NoProbesArrayException(clazz, Settings.PROBES_ARRAY_NAME)

return visitedLinesField.withRemovedFinalModifier {
return visitedLinesField.withAccessibility {
invokeWithStatics.invoke(clazz, methodSignature, arguments, parameters)
}
}
Expand All @@ -57,7 +57,7 @@ object CoverageInstrumentation : Instrumentation<Result<*>> {
val visitedLinesField = clazz.fields.firstOrNull { it.name == probesFieldName }
?: throw NoProbesArrayException(clazz, Settings.PROBES_ARRAY_NAME)

return visitedLinesField.withRemovedFinalModifier {
return visitedLinesField.withAccessibility {
val visitedLines = visitedLinesField.get(null) as? BooleanArray
?: throw CastProbesArrayException()

Expand Down Expand Up @@ -131,5 +131,5 @@ fun ConcreteExecutor<Result<*>, CoverageInstrumentation>.collectCoverage(clazz:
is Protocol.ExceptionInChildProcess -> throw ChildProcessError(it.exception)
else -> throw UnexpectedCommand(it)
}
}!!
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.utbot.instrumentation.examples.mock

import org.utbot.common.withRemovedFinalModifier
import org.utbot.common.withAccessibility
import org.utbot.framework.plugin.api.util.signature
import org.utbot.instrumentation.instrumentation.instrumenter.Instrumenter
import org.utbot.instrumentation.instrumentation.mock.MockClassVisitor
Expand Down Expand Up @@ -58,7 +58,7 @@ class MockHelper(
val isMockField = instrumentedClazz.getDeclaredField(MockConfig.IS_MOCK_FIELD + methodId)
MockGetter.updateMocks(instance, method, mockedValues)

return isMockField.withRemovedFinalModifier {
return isMockField.withAccessibility {
isMockField.set(instance, true)
val res = block(instance)
isMockField.set(instance, false)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.utbot.instrumentation.examples.mock

import org.utbot.common.withRemovedFinalModifier
import org.utbot.common.withAccessibility
import org.utbot.instrumentation.samples.mock.ClassForMockConstructor
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotEquals
Expand All @@ -11,11 +11,11 @@ import org.junit.jupiter.api.Test
class TestConstructorMock {
private fun checkFields(instance: Any, x: Int, s: String?) {
val xField = instance::class.java.getDeclaredField("x")
xField.withRemovedFinalModifier {
xField.withAccessibility {
assertEquals(x, xField.getInt(instance))
}
val sField = instance::class.java.getDeclaredField("s")
sField.withRemovedFinalModifier {
sField.withAccessibility {
assertEquals(s, sField.get(instance))
}
}
Expand Down