Skip to content

Commit 8b70aa1

Browse files
Fix the way of setting static final fields (#466)
1 parent e150db0 commit 8b70aa1

File tree

10 files changed

+192
-33
lines changed

10 files changed

+192
-33
lines changed

utbot-core/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ apply from: "${parent.projectDir}/gradle/include/jvm-project.gradle"
77
dependencies {
88
implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlin_logging_version
99
implementation group: 'net.java.dev.jna', name: 'jna-platform', version: '5.5.0'
10+
11+
testImplementation group: 'junit', name: 'junit', version: junit4_version
1012
}
1113

1214
shadowJar {

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

Lines changed: 28 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
@@ -36,8 +40,9 @@ object Reflection {
3640

3741
inline fun <R> AccessibleObject.withAccessibility(block: () -> R): R {
3842
val prevAccessibility = isAccessible
43+
isAccessible = true
44+
3945
try {
40-
isAccessible = true
4146
return block()
4247
} finally {
4348
isAccessible = prevAccessibility
@@ -47,15 +52,24 @@ inline fun <R> AccessibleObject.withAccessibility(block: () -> R): R {
4752
/**
4853
* Executes given [block] with removed final modifier and restores it back after execution.
4954
* Also sets `isAccessible` to `true` and restores it back.
55+
*
56+
* Please note, that this function doesn't guarantee that reflection calls in the [block] always succeed. The problem is
57+
* that prior calls to reflection may result in caching internal FieldAccessor field which is not suitable for setting
58+
* [this]. But if you didn't call reflection previously, this function should help.
59+
*
60+
* Also note, that primitive static final fields may be inlined, so may not be possible to change.
5061
*/
51-
inline fun<reified R> Field.withRemovedFinalModifier(block: () -> R): R {
62+
inline fun <reified R> Field.withAccessibility(block: () -> R): R {
5263
val prevModifiers = modifiers
64+
val prevAccessibility = isAccessible
65+
66+
isAccessible = true
5367
setModifiers(this, modifiers and Modifier.FINAL.inv())
54-
this.withAccessibility {
55-
try {
56-
return block()
57-
} finally {
58-
setModifiers(this, prevModifiers)
59-
}
68+
69+
try {
70+
return block()
71+
} finally {
72+
isAccessible = prevAccessibility
73+
setModifiers(this, prevModifiers)
6074
}
6175
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package org.utbot.examples.reflection;
2+
3+
public class ClassWithDifferentModifiers {
4+
@SuppressWarnings({"All"})
5+
private int privateField;
6+
7+
private static final Wrapper privateStaticFinalField = new Wrapper(1);
8+
9+
public ClassWithDifferentModifiers() {
10+
privateField = 0;
11+
}
12+
13+
int packagePrivateMethod() {
14+
return 1;
15+
}
16+
17+
private int privateMethod() {
18+
return 1;
19+
}
20+
21+
public static class Wrapper {
22+
public int x;
23+
public Wrapper(int x) {
24+
this.x = x;
25+
}
26+
}
27+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package org.utbot.common
2+
3+
import org.junit.jupiter.api.Assertions.assertEquals
4+
import org.junit.jupiter.api.Assertions.assertNotNull
5+
import org.junit.jupiter.api.Test
6+
import org.utbot.examples.reflection.ClassWithDifferentModifiers
7+
import org.utbot.examples.reflection.ClassWithDifferentModifiers.Wrapper
8+
9+
class ReflectionUtilTest {
10+
private val testedClass = ClassWithDifferentModifiers::class.java
11+
12+
@Test
13+
fun testPackagePrivateInvoke() {
14+
val method = testedClass.declaredMethods.first { it.name == "packagePrivateMethod"}
15+
val instance = ClassWithDifferentModifiers()
16+
17+
method.apply {
18+
withAccessibility {
19+
assertEquals(1, invoke(instance))
20+
}
21+
}
22+
}
23+
24+
@Test
25+
fun testPrivateInvoke() {
26+
val method = testedClass.declaredMethods.first { it.name == "privateMethod"}
27+
val instance = ClassWithDifferentModifiers()
28+
29+
method.apply {
30+
withAccessibility {
31+
assertEquals(1, invoke(instance))
32+
}
33+
}
34+
35+
}
36+
37+
@Test
38+
fun testPrivateFieldSetting() {
39+
val field = testedClass.declaredFields.first { it.name == "privateField" }
40+
val instance = ClassWithDifferentModifiers()
41+
42+
field.apply {
43+
withAccessibility {
44+
set(instance, 0)
45+
}
46+
}
47+
}
48+
49+
@Test
50+
fun testPrivateFieldGetting() {
51+
val field = testedClass.declaredFields.first { it.name == "privateField" }
52+
val instance = ClassWithDifferentModifiers()
53+
54+
field.apply {
55+
withAccessibility {
56+
assertEquals(0, get(instance))
57+
}
58+
}
59+
60+
}
61+
62+
@Test
63+
fun testPrivateFieldGettingAfterSetting() {
64+
val field = testedClass.declaredFields.first { it.name == "privateField" }
65+
val instance = ClassWithDifferentModifiers()
66+
67+
68+
field.apply {
69+
withAccessibility {
70+
set(instance, 1)
71+
}
72+
73+
withAccessibility {
74+
assertEquals(1, get(instance))
75+
}
76+
}
77+
}
78+
79+
@Test
80+
fun testPrivateStaticFinalFieldSetting() {
81+
val field = testedClass.declaredFields.first { it.name == "privateStaticFinalField" }
82+
83+
field.apply {
84+
withAccessibility {
85+
set(null, Wrapper(2))
86+
}
87+
}
88+
}
89+
90+
@Test
91+
fun testPrivateStaticFinalFieldGetting() {
92+
val field = testedClass.declaredFields.first { it.name == "privateStaticFinalField" }
93+
94+
field.apply {
95+
withAccessibility {
96+
val value = get(null) as? Wrapper
97+
assertNotNull(value)
98+
}
99+
}
100+
}
101+
102+
@Test
103+
fun testPrivateStaticFinalFieldGettingAfterSetting() {
104+
val field = testedClass.declaredFields.first { it.name == "privateStaticFinalField" }
105+
106+
field.apply {
107+
withAccessibility {
108+
set(null, Wrapper(3))
109+
}
110+
111+
withAccessibility {
112+
val value = (get(null) as? Wrapper)?.x
113+
assertEquals(3, value)
114+
}
115+
}
116+
}
117+
}

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)