Skip to content

Commit 25c7789

Browse files
Fix the way of setting static final fields
1 parent f47e064 commit 25c7789

File tree

7 files changed

+44
-33
lines changed

7 files changed

+44
-33
lines changed

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

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import java.lang.reflect.AccessibleObject
55
import java.lang.reflect.Field
66
import java.lang.reflect.Modifier
77
import sun.misc.Unsafe
8+
import java.lang.reflect.Method
89

910
object Reflection {
1011
val unsafe: Unsafe
@@ -15,15 +16,18 @@ object Reflection {
1516
unsafe = f.get(null) as Unsafe
1617
}
1718

19+
private val getDeclaredFields0Method: Method =
20+
Class::class.java.getDeclaredMethod("getDeclaredFields0", Boolean::class.java).apply {
21+
isAccessible = true
22+
}
23+
24+
@Suppress("UNCHECKED_CAST")
25+
private val fields: Array<Field> =
26+
getDeclaredFields0Method.invoke(Field::class.java, false) as Array<Field>
1827

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

2832
init {
2933
modifiersField.isAccessible = true
@@ -37,7 +41,6 @@ object Reflection {
3741
inline fun <R> AccessibleObject.withAccessibility(block: () -> R): R {
3842
val prevAccessibility = isAccessible
3943
try {
40-
isAccessible = true
4144
return block()
4245
} finally {
4346
isAccessible = prevAccessibility
@@ -47,15 +50,24 @@ inline fun <R> AccessibleObject.withAccessibility(block: () -> R): R {
4750
/**
4851
* Executes given [block] with removed final modifier and restores it back after execution.
4952
* Also sets `isAccessible` to `true` and restores it back.
53+
*
54+
* Please note, that this function doesn't guarantee that reflection calls in the [block] always succeed. The problem is
55+
* that prior calls to reflection may result in caching internal FieldAccessor field which is not suitable for setting
56+
* [this]. But if you didn't call reflection previously, this function should help.
57+
*
58+
* Also note, that primitive static final fields may be inlined, so may not be possible to change.
5059
*/
51-
inline fun<reified R> Field.withRemovedFinalModifier(block: () -> R): R {
60+
inline fun <reified R> Field.withAccessibility(block: () -> R): R {
5261
val prevModifiers = modifiers
62+
val prevAccessibility = isAccessible
63+
64+
isAccessible = true
5365
setModifiers(this, modifiers and Modifier.FINAL.inv())
54-
this.withAccessibility {
55-
try {
56-
return block()
57-
} finally {
58-
setModifiers(this, prevModifiers)
59-
}
66+
67+
try {
68+
return block()
69+
} finally {
70+
isAccessible = prevAccessibility
71+
setModifiers(this, prevModifiers)
6072
}
6173
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package org.utbot.framework.concrete
22

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

39-
isMockField.withRemovedFinalModifier {
39+
isMockField.withAccessibility {
4040
isMockField.set(instance, true)
4141
}
4242

@@ -46,7 +46,7 @@ class MethodMockController(
4646
}
4747

4848
override fun close() {
49-
isMockField.withRemovedFinalModifier {
49+
isMockField.withAccessibility {
5050
isMockField.set(instance, false)
5151
}
5252
}

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package org.utbot.framework.concrete
33
import org.utbot.common.StopWatch
44
import org.utbot.common.ThreadBasedExecutor
55
import org.utbot.common.withAccessibility
6-
import org.utbot.common.withRemovedFinalModifier
76
import org.utbot.framework.UtSettings
87
import org.utbot.framework.assemble.AssembleModelGenerator
98
import org.utbot.framework.plugin.api.Coverage
@@ -260,7 +259,7 @@ object UtExecutionInstrumentation : Instrumentation<UtConcreteExecutionResult> {
260259
try {
261260
staticFields.forEach { (fieldId, value) ->
262261
fieldId.field.run {
263-
withRemovedFinalModifier {
262+
withAccessibility {
264263
savedFields[fieldId] = get(null)
265264
set(null, value)
266265
}
@@ -270,7 +269,7 @@ object UtExecutionInstrumentation : Instrumentation<UtConcreteExecutionResult> {
270269
} finally {
271270
savedFields.forEach { (fieldId, value) ->
272271
fieldId.field.run {
273-
withRemovedFinalModifier {
272+
withAccessibility {
274273
set(null, value)
275274
}
276275
}

utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/InvokeWithStaticsInstrumentation.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package org.utbot.instrumentation.instrumentation
22

3-
import org.utbot.common.withRemovedFinalModifier
3+
import org.utbot.common.withAccessibility
44
import org.utbot.framework.plugin.api.util.field
55
import org.utbot.instrumentation.util.StaticEnvironment
66
import java.lang.reflect.Field
@@ -47,7 +47,7 @@ class InvokeWithStaticsInstrumentation : Instrumentation<Result<*>> {
4747
staticEnvironment?.run {
4848
listOfFields.forEach { (fieldId, value) ->
4949
fieldId.field.run {
50-
withRemovedFinalModifier {
50+
withAccessibility {
5151
set(null, value)
5252
}
5353
}
@@ -75,14 +75,14 @@ class InvokeWithStaticsInstrumentation : Instrumentation<Result<*>> {
7575
init {
7676
val staticFields = clazz.declaredFields
7777
.filter { checkField(it) } // TODO: think on this
78-
.associate { it.name to it.withRemovedFinalModifier { it.get(null) } }
78+
.associate { it.name to it.withAccessibility { it.get(null) } }
7979
savedFields = staticFields
8080
}
8181

8282
fun restore() {
8383
clazz.declaredFields
8484
.filter { checkField(it) }
85-
.forEach { it.withRemovedFinalModifier { it.set(null, savedFields[it.name]) } }
85+
.forEach { it.withAccessibility { it.set(null, savedFields[it.name]) } }
8686
}
8787
}
8888
}

utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/coverage/CoverageInstrumentation.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package org.utbot.instrumentation.instrumentation.coverage
22

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

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

60-
return visitedLinesField.withRemovedFinalModifier {
60+
return visitedLinesField.withAccessibility {
6161
val visitedLines = visitedLinesField.get(null) as? BooleanArray
6262
?: throw CastProbesArrayException()
6363

@@ -131,5 +131,5 @@ fun ConcreteExecutor<Result<*>, CoverageInstrumentation>.collectCoverage(clazz:
131131
is Protocol.ExceptionInChildProcess -> throw ChildProcessError(it.exception)
132132
else -> throw UnexpectedCommand(it)
133133
}
134-
}!!
134+
}
135135
}

utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/examples/mock/MockHelper.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package org.utbot.instrumentation.examples.mock
22

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

61-
return isMockField.withRemovedFinalModifier {
61+
return isMockField.withAccessibility {
6262
isMockField.set(instance, true)
6363
val res = block(instance)
6464
isMockField.set(instance, false)

utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/examples/mock/TestConstructorMock.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package org.utbot.instrumentation.examples.mock
22

3-
import org.utbot.common.withRemovedFinalModifier
3+
import org.utbot.common.withAccessibility
44
import org.utbot.instrumentation.samples.mock.ClassForMockConstructor
55
import org.junit.jupiter.api.Assertions.assertEquals
66
import org.junit.jupiter.api.Assertions.assertNotEquals
@@ -11,11 +11,11 @@ import org.junit.jupiter.api.Test
1111
class TestConstructorMock {
1212
private fun checkFields(instance: Any, x: Int, s: String?) {
1313
val xField = instance::class.java.getDeclaredField("x")
14-
xField.withRemovedFinalModifier {
14+
xField.withAccessibility {
1515
assertEquals(x, xField.getInt(instance))
1616
}
1717
val sField = instance::class.java.getDeclaredField("s")
18-
sField.withRemovedFinalModifier {
18+
sField.withAccessibility {
1919
assertEquals(s, sField.get(instance))
2020
}
2121
}

0 commit comments

Comments
 (0)