Skip to content

Commit bcece73

Browse files
authored
Replace Kotlin getters/setters with property access in codegen #496 (#1002)
1 parent 8fa3bb5 commit bcece73

File tree

10 files changed

+128
-23
lines changed

10 files changed

+128
-23
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,12 @@ fun ClassId.defaultValueModel(): UtModel = when (this) {
387387
else -> UtNullModel(this)
388388
}
389389

390+
val ClassId.allDeclaredFieldIds: Sequence<FieldId>
391+
get() =
392+
generateSequence(this.jClass) { it.superclass }
393+
.flatMap { it.declaredFields.asSequence() }
394+
.map { it.fieldId }
395+
390396
// FieldId utils
391397
val FieldId.safeJField: Field?
392398
get() = declaringClass.jClass.declaredFields.firstOrNull { it.name == name }

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import org.utbot.framework.codegen.model.tree.CgValue
3333
import org.utbot.framework.codegen.model.tree.CgVariable
3434
import org.utbot.framework.codegen.model.util.at
3535
import org.utbot.framework.codegen.model.util.isAccessibleFrom
36+
import org.utbot.framework.codegen.model.util.canBeReadFrom
3637
import org.utbot.framework.codegen.model.util.nullLiteral
3738
import org.utbot.framework.codegen.model.util.resolve
3839
import org.utbot.framework.plugin.api.BuiltinConstructorId
@@ -293,7 +294,7 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA
293294

294295
private fun FieldId.accessSuitability(accessor: CgExpression?): FieldAccessorSuitability {
295296
// Check field accessibility.
296-
if (!isAccessibleFrom(testClassPackageName)) {
297+
if (!canBeReadFrom(context)) {
297298
return ReflectionOnly
298299
}
299300

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import org.utbot.framework.codegen.model.tree.CgValue
1818
import org.utbot.framework.codegen.model.tree.CgVariable
1919
import org.utbot.framework.codegen.model.util.at
2020
import org.utbot.framework.codegen.model.util.isAccessibleFrom
21+
import org.utbot.framework.codegen.model.util.canBeReadFrom
2122
import org.utbot.framework.codegen.model.util.stringLiteral
2223
import org.utbot.framework.fields.ArrayElementAccess
2324
import org.utbot.framework.fields.FieldAccess
@@ -182,7 +183,7 @@ internal class CgFieldStateManagerImpl(val context: CgContext)
182183
for ((index, fieldPathElement) in path.withIndex()) {
183184
when (fieldPathElement) {
184185
is FieldAccess -> {
185-
if (!fieldPathElement.field.isAccessibleFrom(testClassPackageName)) {
186+
if (!fieldPathElement.field.canBeReadFrom(context)) {
186187
lastAccessibleIndex = index - 1
187188
break
188189
}
@@ -246,7 +247,7 @@ internal class CgFieldStateManagerImpl(val context: CgContext)
246247

247248
private fun variableForStaticFieldState(owner: ClassId, fieldPath: FieldPath, customName: String?): CgVariable {
248249
val firstField = (fieldPath.elements.first() as FieldAccess).field
249-
val firstAccessor = if (owner.isAccessibleFrom(testClassPackageName) && firstField.isAccessibleFrom(testClassPackageName)) {
250+
val firstAccessor = if (owner.isAccessibleFrom(testClassPackageName) && firstField.canBeReadFrom(context)) {
250251
owner[firstField]
251252
} else {
252253
// TODO: there is a function getClassOf() for these purposes, but it is not accessible from here for now

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,10 @@ import org.utbot.framework.codegen.model.tree.buildParameterizedTestDataProvider
6666
import org.utbot.framework.codegen.model.tree.buildTestMethod
6767
import org.utbot.framework.codegen.model.tree.convertDocToCg
6868
import org.utbot.framework.codegen.model.tree.toStatement
69-
import org.utbot.framework.codegen.model.util.canBeSetIn
69+
import org.utbot.framework.codegen.model.util.canBeSetFrom
7070
import org.utbot.framework.codegen.model.util.equalTo
7171
import org.utbot.framework.codegen.model.util.inc
72-
import org.utbot.framework.codegen.model.util.isAccessibleFrom
72+
import org.utbot.framework.codegen.model.util.canBeReadFrom
7373
import org.utbot.framework.codegen.model.util.length
7474
import org.utbot.framework.codegen.model.util.lessThan
7575
import org.utbot.framework.codegen.model.util.nullLiteral
@@ -218,7 +218,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
218218
val accessibleStaticFields = statics.accessibleFields()
219219
for ((field, _) in accessibleStaticFields) {
220220
val declaringClass = field.declaringClass
221-
val fieldAccessible = field.isAccessibleFrom(testClassPackageName)
221+
val fieldAccessible = field.canBeReadFrom(context)
222222

223223
// prevValue is nullable if not accessible because of getStaticFieldValue(..) : Any?
224224
val prevValue = newVar(
@@ -243,7 +243,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
243243
val accessibleStaticFields = statics.accessibleFields()
244244
for ((field, model) in accessibleStaticFields) {
245245
val declaringClass = field.declaringClass
246-
val fieldAccessible = field.canBeSetIn(testClassPackageName)
246+
val fieldAccessible = field.canBeSetFrom(context)
247247

248248
val fieldValue = if (isParametrized) {
249249
currentMethodParameters[CgParameterKind.Statics(model)]
@@ -264,7 +264,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
264264

265265
private fun recoverStaticFields() {
266266
for ((field, prevValue) in prevStaticFieldValues.accessibleFields()) {
267-
if (field.canBeSetIn(testClassPackageName)) {
267+
if (field.canBeSetFrom(context)) {
268268
field.declaringClass[field] `=` prevValue
269269
} else {
270270
val declaringClass = getClassOf(field.declaringClass)
@@ -1039,7 +1039,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c
10391039
private fun FieldId.getAccessExpression(variable: CgVariable): CgExpression =
10401040
// Can directly access field only if it is declared in variable class (or in its ancestors)
10411041
// and is accessible from current package
1042-
if (variable.type.hasField(this) && isAccessibleFrom(testClassPackageName)) {
1042+
if (variable.type.hasField(this) && canBeReadFrom(context)) {
10431043
if (jField.isStatic) CgStaticFieldAccess(this) else CgFieldAccess(variable, this)
10441044
} else {
10451045
utilsClassId[getFieldValue](variable, this.declaringClass.name, this.name)

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

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import org.utbot.framework.codegen.model.constructor.util.isDefaultValueOf
1414
import org.utbot.framework.codegen.model.constructor.util.isNotDefaultValueOf
1515
import org.utbot.framework.codegen.model.constructor.util.typeCast
1616
import org.utbot.framework.codegen.model.tree.CgAllocateArray
17+
import org.utbot.framework.codegen.model.tree.CgAssignment
1718
import org.utbot.framework.codegen.model.tree.CgDeclaration
1819
import org.utbot.framework.codegen.model.tree.CgEnumConstantAccess
1920
import org.utbot.framework.codegen.model.tree.CgExecutableCall
@@ -22,18 +23,22 @@ import org.utbot.framework.codegen.model.tree.CgFieldAccess
2223
import org.utbot.framework.codegen.model.tree.CgGetJavaClass
2324
import org.utbot.framework.codegen.model.tree.CgLiteral
2425
import org.utbot.framework.codegen.model.tree.CgMethodCall
26+
import org.utbot.framework.codegen.model.tree.CgStatement
2527
import org.utbot.framework.codegen.model.tree.CgStaticFieldAccess
2628
import org.utbot.framework.codegen.model.tree.CgValue
2729
import org.utbot.framework.codegen.model.tree.CgVariable
2830
import org.utbot.framework.codegen.model.util.at
29-
import org.utbot.framework.codegen.model.util.canBeSetIn
31+
import org.utbot.framework.codegen.model.util.canBeSetFrom
32+
import org.utbot.framework.codegen.model.util.fieldThatIsGotWith
33+
import org.utbot.framework.codegen.model.util.fieldThatIsSetWith
3034
import org.utbot.framework.codegen.model.util.inc
3135
import org.utbot.framework.codegen.model.util.isAccessibleFrom
3236
import org.utbot.framework.codegen.model.util.lessThan
3337
import org.utbot.framework.codegen.model.util.nullLiteral
3438
import org.utbot.framework.codegen.model.util.resolve
3539
import org.utbot.framework.plugin.api.BuiltinClassId
3640
import org.utbot.framework.plugin.api.ClassId
41+
import org.utbot.framework.plugin.api.CodegenLanguage
3742
import org.utbot.framework.plugin.api.ConstructorId
3843
import org.utbot.framework.plugin.api.MethodId
3944
import org.utbot.framework.plugin.api.UtArrayModel
@@ -188,7 +193,7 @@ internal class CgVariableConstructor(val context: CgContext) :
188193
// byteBuffer is field of type ByteBuffer and upper line is incorrect
189194
val canFieldBeDirectlySetByVariableAndFieldTypeRestrictions =
190195
fieldFromVariableSpecifiedType != null && fieldFromVariableSpecifiedType.type.id == variableForField.type
191-
if (canFieldBeDirectlySetByVariableAndFieldTypeRestrictions && fieldId.canBeSetIn(testClassPackageName)) {
196+
if (canFieldBeDirectlySetByVariableAndFieldTypeRestrictions && fieldId.canBeSetFrom(context)) {
192197
// TODO: check if it is correct to use declaringClass of a field here
193198
val fieldAccess = if (field.isStatic) CgStaticFieldAccess(fieldId) else CgFieldAccess(obj, fieldId)
194199
fieldAccess `=` variableForField
@@ -212,7 +217,12 @@ internal class CgVariableConstructor(val context: CgContext) :
212217
instance[statementModel.fieldId] `=` declareOrGet(statementModel.fieldModel)
213218
}
214219
is UtExecutableCallModel -> {
215-
+createCgExecutableCallFromUtExecutableCall(statementModel)
220+
val call = createCgExecutableCallFromUtExecutableCall(statementModel)
221+
val equivalentFieldAccess = replaceCgExecutableCallWithFieldAccessIfNeeded(call)
222+
if (equivalentFieldAccess != null)
223+
+equivalentFieldAccess
224+
else
225+
+call
216226
}
217227
}
218228
}
@@ -261,6 +271,37 @@ internal class CgVariableConstructor(val context: CgContext) :
261271
return cgCall
262272
}
263273

274+
/**
275+
* If executable is getter/setter that should be syntactically replaced with field access
276+
* (e.g., getter/setter generated by Kotlin in Kotlin code), this method returns [CgStatement]
277+
* with which [call] should be replaced.
278+
*
279+
* Otherwise, returns null.
280+
*/
281+
private fun replaceCgExecutableCallWithFieldAccessIfNeeded(call: CgExecutableCall): CgStatement? {
282+
when (context.codegenLanguage) {
283+
CodegenLanguage.JAVA -> return null
284+
CodegenLanguage.KOTLIN -> {
285+
if (call !is CgMethodCall)
286+
return null
287+
288+
val caller = call.caller ?: return null
289+
290+
caller.type.fieldThatIsSetWith(call.executableId)?.let {
291+
return CgAssignment(caller[it], call.arguments.single())
292+
}
293+
caller.type.fieldThatIsGotWith(call.executableId)?.let {
294+
require(call.arguments.isEmpty()) {
295+
"Method $call was detected as getter for $it, but its arguments list isn't empty"
296+
}
297+
return caller[it]
298+
}
299+
300+
return null
301+
}
302+
}
303+
}
304+
264305
/**
265306
* Makes a replacement of constructor call to instantiate a primitive wrapper
266307
* with direct setting of the value. The reason is that in Kotlin constructors

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import org.utbot.framework.codegen.model.constructor.builtin.anyLong
1313
import org.utbot.framework.codegen.model.constructor.builtin.anyOfClass
1414
import org.utbot.framework.codegen.model.constructor.builtin.anyShort
1515
import org.utbot.framework.codegen.model.constructor.builtin.argumentMatchersClassId
16-
import org.utbot.framework.codegen.model.constructor.builtin.forName
1716
import org.utbot.framework.codegen.model.constructor.builtin.mockMethodId
1817
import org.utbot.framework.codegen.model.constructor.builtin.mockedConstructionContextClassId
1918
import org.utbot.framework.codegen.model.constructor.builtin.mockitoClassId
@@ -24,7 +23,6 @@ import org.utbot.framework.codegen.model.constructor.context.CgContextOwner
2423
import org.utbot.framework.codegen.model.constructor.util.CgComponents
2524
import org.utbot.framework.codegen.model.constructor.util.CgStatementConstructor
2625
import org.utbot.framework.codegen.model.constructor.util.CgStatementConstructorImpl
27-
import org.utbot.framework.codegen.model.constructor.util.classCgClassId
2826
import org.utbot.framework.codegen.model.constructor.util.hasAmbiguousOverloadsOf
2927
import org.utbot.framework.codegen.model.tree.CgAnonymousFunction
3028
import org.utbot.framework.codegen.model.tree.CgAssignment
@@ -33,7 +31,6 @@ import org.utbot.framework.codegen.model.tree.CgConstructorCall
3331
import org.utbot.framework.codegen.model.tree.CgDeclaration
3432
import org.utbot.framework.codegen.model.tree.CgExecutableCall
3533
import org.utbot.framework.codegen.model.tree.CgExpression
36-
import org.utbot.framework.codegen.model.tree.CgGetJavaClass
3734
import org.utbot.framework.codegen.model.tree.CgLiteral
3835
import org.utbot.framework.codegen.model.tree.CgMethodCall
3936
import org.utbot.framework.codegen.model.tree.CgParameterDeclaration
@@ -63,7 +60,6 @@ import org.utbot.framework.plugin.api.util.byteClassId
6360
import org.utbot.framework.plugin.api.util.charClassId
6461
import org.utbot.framework.plugin.api.util.doubleClassId
6562
import org.utbot.framework.plugin.api.util.floatClassId
66-
import org.utbot.framework.plugin.api.util.id
6763
import org.utbot.framework.plugin.api.util.intClassId
6864
import org.utbot.framework.plugin.api.util.longClassId
6965
import org.utbot.framework.plugin.api.util.shortClassId

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ import org.utbot.framework.codegen.StaticImport
55
import org.utbot.framework.codegen.model.constructor.context.CgContextOwner
66
import org.utbot.framework.codegen.model.tree.CgClassId
77
import org.utbot.framework.codegen.model.tree.CgExpression
8-
import org.utbot.framework.codegen.model.tree.CgGetClass
9-
import org.utbot.framework.codegen.model.tree.CgGetJavaClass
108
import org.utbot.framework.codegen.model.tree.CgTypeCast
119
import org.utbot.framework.codegen.model.tree.CgValue
1210
import org.utbot.framework.codegen.model.tree.CgVariable

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package org.utbot.framework.codegen.model.util
22

33
import org.utbot.framework.plugin.api.ClassId
4+
import org.utbot.framework.plugin.api.FieldId
5+
import org.utbot.framework.plugin.api.MethodId
6+
import org.utbot.framework.plugin.api.util.allDeclaredFieldIds
47
import org.utbot.framework.plugin.api.util.id
58
import org.utbot.framework.plugin.api.util.isArray
69

@@ -28,4 +31,16 @@ infix fun ClassId.isAccessibleFrom(packageName: String): Boolean {
2831
} else {
2932
isPublic || (this.packageName == packageName && (isPackagePrivate || isProtected))
3033
}
31-
}
34+
}
35+
36+
/**
37+
* Returns field of [this], such that [methodId] is a getter for it (or null if methodId doesn't represent a getter)
38+
*/
39+
internal fun ClassId.fieldThatIsGotWith(methodId: MethodId): FieldId? =
40+
allDeclaredFieldIds.singleOrNull { !it.isStatic && it.getter == methodId }
41+
42+
/**
43+
* Returns field of [this], such that [methodId] is a setter for it (or null if methodId doesn't represent a setter)
44+
*/
45+
internal fun ClassId.fieldThatIsSetWith(methodId: MethodId): FieldId? =
46+
allDeclaredFieldIds.singleOrNull { !it.isStatic && it.setter == methodId }
Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,70 @@
11
package org.utbot.framework.codegen.model.util
22

3+
import org.utbot.framework.codegen.model.constructor.context.CgContext
4+
import org.utbot.framework.plugin.api.CodegenLanguage
35
import org.utbot.framework.plugin.api.FieldId
6+
import org.utbot.framework.plugin.api.MethodId
7+
import org.utbot.framework.plugin.api.util.voidClassId
48

59
/**
610
* For now we will count field accessible if it is not private and its class is also accessible,
711
* because we generate tests in the same package with the class under test,
812
* which means we can access public, protected and package-private fields
913
*
10-
* @param packageName name of the package we check accessibility from
14+
* @param context context in which code is generated (it is needed because the method needs to know package and language)
1115
*/
12-
infix fun FieldId.isAccessibleFrom(packageName: String): Boolean {
16+
// TODO: change parameter from packageName: String to context: CgContext in ClassId.isAccessibleFrom and ExecutableId.isAccessibleFrom ?
17+
private fun FieldId.isAccessibleFrom(context: CgContext): Boolean {
18+
val packageName = context.testClassPackageName
1319
val isClassAccessible = declaringClass.isAccessibleFrom(packageName)
1420
val isAccessibleByVisibility = isPublic || (declaringClass.packageName == packageName && (isPackagePrivate || isProtected))
1521
val isAccessibleFromPackageByModifiers = isAccessibleByVisibility && !isSynthetic
1622

1723
return isClassAccessible && isAccessibleFromPackageByModifiers
1824
}
1925

26+
private fun FieldId.canBeReadViaGetterFrom(context: CgContext): Boolean =
27+
declaringClass.allMethods.contains(getter) && getter.isAccessibleFrom(context.testClassPackageName)
28+
29+
/**
30+
* Returns whether you can read field's value without reflection
31+
*/
32+
internal infix fun FieldId.canBeReadFrom(context: CgContext): Boolean {
33+
if (context.codegenLanguage == CodegenLanguage.KOTLIN) {
34+
// Kotlin will allow direct field access for non-static fields with accessible getter
35+
if (!isStatic && canBeReadViaGetterFrom(context))
36+
return true
37+
}
38+
39+
return isAccessibleFrom(context)
40+
}
41+
42+
private fun FieldId.canBeSetViaSetterFrom(context: CgContext): Boolean =
43+
declaringClass.allMethods.contains(setter) && setter.isAccessibleFrom(context.testClassPackageName)
44+
2045
/**
2146
* Whether or not a field can be set without reflection
2247
*/
23-
fun FieldId.canBeSetIn(packageName: String): Boolean = isAccessibleFrom(packageName) && !isFinal
48+
internal fun FieldId.canBeSetFrom(context: CgContext): Boolean {
49+
if (context.codegenLanguage == CodegenLanguage.KOTLIN) {
50+
// Kotlin will allow direct write access if both getter and setter is defined
51+
// !isAccessibleFrom(context) is important here because above rule applies to final fields only if they are not accessible in Java terms
52+
if (!isAccessibleFrom(context) && !isStatic && canBeReadViaGetterFrom(context) && canBeSetViaSetterFrom(context)) {
53+
return true
54+
}
55+
}
56+
57+
return isAccessibleFrom(context) && !isFinal
58+
}
59+
60+
/**
61+
* The default getter method for field (the one which is generated by Kotlin compiler)
62+
*/
63+
val FieldId.getter: MethodId
64+
get() = MethodId(declaringClass, "get${name.replaceFirstChar { it.uppercase() } }", type, emptyList())
65+
66+
/**
67+
* The default setter method for field (the one which is generated by Kotlin compiler)
68+
*/
69+
val FieldId.setter: MethodId
70+
get() = MethodId(declaringClass, "set${name.replaceFirstChar { it.uppercase() } }", voidClassId, listOf(type))

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ private fun createInstance(visibility: Visibility, language: CodegenLanguage): S
343343
}
344344
CodegenLanguage.KOTLIN -> {
345345
"""
346-
${visibility by language}fun createInstance(className: String): kotlin.Any? {
346+
${visibility by language}fun createInstance(className: String): kotlin.Any {
347347
val clazz: Class<*> = Class.forName(className)
348348
return Class.forName("sun.misc.Unsafe").getDeclaredMethod("allocateInstance", Class::class.java)
349349
.invoke(getUnsafeInstance(), clazz)

0 commit comments

Comments
 (0)