From 595001209986bf87e0d9e7e58d4a5ebdd85ab007 Mon Sep 17 00:00:00 2001 From: Ivan Volkov Date: Tue, 20 Sep 2022 12:24:49 +0300 Subject: [PATCH 1/9] Replace Kotlin getters/setters with property access in codegen --- .../utbot/framework/plugin/api/util/IdUtil.kt | 6 ++++ .../tree/CgCallableAccessManager.kt | 2 +- .../constructor/tree/CgFieldStateManager.kt | 4 +-- .../constructor/tree/CgMethodConstructor.kt | 8 ++--- .../constructor/tree/CgVariableConstructor.kt | 28 ++++++++++++++-- .../codegen/model/util/ClassIdUtil.kt | 17 +++++++++- .../codegen/model/util/FieldIdUtil.kt | 33 +++++++++++++++++-- 7 files changed, 85 insertions(+), 13 deletions(-) diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt index 4addbd3f0d..62e077cfdc 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt @@ -387,6 +387,12 @@ fun ClassId.defaultValueModel(): UtModel = when (this) { else -> UtNullModel(this) } +val ClassId.allDeclaredFieldIds: Sequence + get() = + generateSequence(this.jClass) { it.superclass } + .flatMap { it.declaredFields.asSequence() } + .map { it.fieldId } + // FieldId utils val FieldId.safeJField: Field? get() = declaringClass.jClass.declaredFields.firstOrNull { it.name == name } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt index ada6563223..ff3a3863da 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt @@ -293,7 +293,7 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA private fun FieldId.accessSuitability(accessor: CgExpression?): FieldAccessorSuitability { // Check field accessibility. - if (!isAccessibleFrom(testClassPackageName)) { + if (!isAccessibleFrom(context)) { return ReflectionOnly } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgFieldStateManager.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgFieldStateManager.kt index 59e4c9561e..64d2874999 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgFieldStateManager.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgFieldStateManager.kt @@ -182,7 +182,7 @@ internal class CgFieldStateManagerImpl(val context: CgContext) for ((index, fieldPathElement) in path.withIndex()) { when (fieldPathElement) { is FieldAccess -> { - if (!fieldPathElement.field.isAccessibleFrom(testClassPackageName)) { + if (!fieldPathElement.field.isAccessibleFrom(context)) { lastAccessibleIndex = index - 1 break } @@ -246,7 +246,7 @@ internal class CgFieldStateManagerImpl(val context: CgContext) private fun variableForStaticFieldState(owner: ClassId, fieldPath: FieldPath, customName: String?): CgVariable { val firstField = (fieldPath.elements.first() as FieldAccess).field - val firstAccessor = if (owner.isAccessibleFrom(testClassPackageName) && firstField.isAccessibleFrom(testClassPackageName)) { + val firstAccessor = if (owner.isAccessibleFrom(testClassPackageName) && firstField.isAccessibleFrom(context)) { owner[firstField] } else { // TODO: there is a function getClassOf() for these purposes, but it is not accessible from here for now diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt index 367c68a8f5..ec96677d7a 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt @@ -218,7 +218,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c val accessibleStaticFields = statics.accessibleFields() for ((field, _) in accessibleStaticFields) { val declaringClass = field.declaringClass - val fieldAccessible = field.isAccessibleFrom(testClassPackageName) + val fieldAccessible = field.isAccessibleFrom(context) // prevValue is nullable if not accessible because of getStaticFieldValue(..) : Any? val prevValue = newVar( @@ -243,7 +243,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c val accessibleStaticFields = statics.accessibleFields() for ((field, model) in accessibleStaticFields) { val declaringClass = field.declaringClass - val fieldAccessible = field.canBeSetIn(testClassPackageName) + val fieldAccessible = field.canBeSetIn(context) val fieldValue = if (isParametrized) { currentMethodParameters[CgParameterKind.Statics(model)] @@ -264,7 +264,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c private fun recoverStaticFields() { for ((field, prevValue) in prevStaticFieldValues.accessibleFields()) { - if (field.canBeSetIn(testClassPackageName)) { + if (field.canBeSetIn(context)) { field.declaringClass[field] `=` prevValue } else { val declaringClass = getClassOf(field.declaringClass) @@ -1039,7 +1039,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c private fun FieldId.getAccessExpression(variable: CgVariable): CgExpression = // Can directly access field only if it is declared in variable class (or in its ancestors) // and is accessible from current package - if (variable.type.hasField(this) && isAccessibleFrom(testClassPackageName)) { + if (variable.type.hasField(this) && isAccessibleFrom(context)) { if (jField.isStatic) CgStaticFieldAccess(this) else CgFieldAccess(variable, this) } else { utilsClassId[getFieldValue](variable, this.declaringClass.name, this.name) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt index f8b453b55a..c7f7aea1de 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt @@ -14,6 +14,7 @@ import org.utbot.framework.codegen.model.constructor.util.isDefaultValueOf import org.utbot.framework.codegen.model.constructor.util.isNotDefaultValueOf import org.utbot.framework.codegen.model.constructor.util.typeCast import org.utbot.framework.codegen.model.tree.CgAllocateArray +import org.utbot.framework.codegen.model.tree.CgAssignment import org.utbot.framework.codegen.model.tree.CgDeclaration import org.utbot.framework.codegen.model.tree.CgEnumConstantAccess import org.utbot.framework.codegen.model.tree.CgExecutableCall @@ -22,11 +23,14 @@ import org.utbot.framework.codegen.model.tree.CgFieldAccess import org.utbot.framework.codegen.model.tree.CgGetJavaClass import org.utbot.framework.codegen.model.tree.CgLiteral import org.utbot.framework.codegen.model.tree.CgMethodCall +import org.utbot.framework.codegen.model.tree.CgStatement import org.utbot.framework.codegen.model.tree.CgStaticFieldAccess import org.utbot.framework.codegen.model.tree.CgValue import org.utbot.framework.codegen.model.tree.CgVariable import org.utbot.framework.codegen.model.util.at import org.utbot.framework.codegen.model.util.canBeSetIn +import org.utbot.framework.codegen.model.util.fieldThisIsGetterFor +import org.utbot.framework.codegen.model.util.fieldThisIsSetterFor import org.utbot.framework.codegen.model.util.inc import org.utbot.framework.codegen.model.util.isAccessibleFrom import org.utbot.framework.codegen.model.util.lessThan @@ -34,6 +38,7 @@ import org.utbot.framework.codegen.model.util.nullLiteral import org.utbot.framework.codegen.model.util.resolve import org.utbot.framework.plugin.api.BuiltinClassId import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.CodegenLanguage import org.utbot.framework.plugin.api.ConstructorId import org.utbot.framework.plugin.api.MethodId import org.utbot.framework.plugin.api.UtArrayModel @@ -188,7 +193,7 @@ internal class CgVariableConstructor(val context: CgContext) : // byteBuffer is field of type ByteBuffer and upper line is incorrect val canFieldBeDirectlySetByVariableAndFieldTypeRestrictions = fieldFromVariableSpecifiedType != null && fieldFromVariableSpecifiedType.type.id == variableForField.type - if (canFieldBeDirectlySetByVariableAndFieldTypeRestrictions && fieldId.canBeSetIn(testClassPackageName)) { + if (canFieldBeDirectlySetByVariableAndFieldTypeRestrictions && fieldId.canBeSetIn(context)) { // TODO: check if it is correct to use declaringClass of a field here val fieldAccess = if (field.isStatic) CgStaticFieldAccess(fieldId) else CgFieldAccess(obj, fieldId) fieldAccess `=` variableForField @@ -212,7 +217,8 @@ internal class CgVariableConstructor(val context: CgContext) : instance[statementModel.fieldId] `=` declareOrGet(statementModel.fieldModel) } is UtExecutableCallModel -> { - +createCgExecutableCallFromUtExecutableCall(statementModel) + val call = createCgExecutableCallFromUtExecutableCall(statementModel) + +replaceCgExecutableCallWithFieldAccessIfNeeded(call) } } } @@ -236,6 +242,7 @@ internal class CgVariableConstructor(val context: CgContext) : val initExpr = if (isPrimitiveWrapperOrString(type)) { cgLiteralForWrapper(params) } else { + // TODO: if instantiation chain could be a setter call, we need to replace it in Kotlin createCgExecutableCallFromUtExecutableCall(executableCall) } newVar(type, model, baseName) { @@ -261,6 +268,23 @@ internal class CgVariableConstructor(val context: CgContext) : return cgCall } + private fun replaceCgExecutableCallWithFieldAccessIfNeeded(call: CgExecutableCall): CgStatement { + if (call !is CgMethodCall || context.codegenLanguage != CodegenLanguage.KOTLIN) + return call + + val caller = call.caller ?: return call + + caller.type.fieldThisIsSetterFor(call.executableId)?.let { + return CgAssignment(caller[it], call.arguments.single()) + } + caller.type.fieldThisIsGetterFor(call.executableId)?.let { + require(call.arguments.isEmpty()) + return caller[it] + } + + return call + } + /** * Makes a replacement of constructor call to instantiate a primitive wrapper * with direct setting of the value. The reason is that in Kotlin constructors diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/ClassIdUtil.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/ClassIdUtil.kt index 13eab73607..d9cd8efd6e 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/ClassIdUtil.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/ClassIdUtil.kt @@ -1,6 +1,9 @@ package org.utbot.framework.codegen.model.util import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.util.allDeclaredFieldIds import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.isArray @@ -28,4 +31,16 @@ infix fun ClassId.isAccessibleFrom(packageName: String): Boolean { } else { isPublic || (this.packageName == packageName && (isPackagePrivate || isProtected)) } -} \ No newline at end of file +} + +/** + * Returns field of [this], such that [methodId] is a getter for it (or null if methodId doesn't represent a getter) + */ +internal fun ClassId.fieldThisIsGetterFor(methodId: MethodId): FieldId? = + allDeclaredFieldIds.firstOrNull { it.getter == methodId } + +/** + * Returns field of [this], such that [methodId] is a setter for it (or null if methodId doesn't represent a setter) + */ +internal fun ClassId.fieldThisIsSetterFor(methodId: MethodId): FieldId? = + allDeclaredFieldIds.firstOrNull { it.setter == methodId } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/FieldIdUtil.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/FieldIdUtil.kt index ad54a9b5b9..e2e324b531 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/FieldIdUtil.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/FieldIdUtil.kt @@ -1,15 +1,25 @@ package org.utbot.framework.codegen.model.util +import org.utbot.framework.codegen.model.constructor.context.CgContext +import org.utbot.framework.plugin.api.CodegenLanguage import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.util.voidClassId /** * For now we will count field accessible if it is not private and its class is also accessible, * because we generate tests in the same package with the class under test, * which means we can access public, protected and package-private fields * - * @param packageName name of the package we check accessibility from + * @param context context in which code is generated (it is needed because the method needs to know package and language) */ -infix fun FieldId.isAccessibleFrom(packageName: String): Boolean { +// TODO: change parameter from packageName: String to context: CgContext in ClassId.isAccessibleFrom and ExecutableId.isAccessibleFrom ? +internal infix fun FieldId.isAccessibleFrom(context: CgContext): Boolean { + if (context.codegenLanguage == CodegenLanguage.KOTLIN) { + // Here we call field accessible iff its getter is accessible, checks for setter are made in FieldId.canBeSetIn + return declaringClass.allMethods.contains(getter) && getter.isAccessibleFrom(context.testClassPackageName) + } + val packageName = context.testClassPackageName val isClassAccessible = declaringClass.isAccessibleFrom(packageName) val isAccessibleByVisibility = isPublic || (declaringClass.packageName == packageName && (isPackagePrivate || isProtected)) val isAccessibleFromPackageByModifiers = isAccessibleByVisibility && !isSynthetic @@ -20,4 +30,21 @@ infix 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 +internal fun FieldId.canBeSetIn(context: CgContext): Boolean { + if (context.codegenLanguage == CodegenLanguage.KOTLIN) { + return declaringClass.allMethods.contains(setter) && setter.isAccessibleFrom(context.testClassPackageName) + } + return isAccessibleFrom(context) && !isFinal +} + +/** + * The default getter method for field (the one which is generated by Kotlin compiler) + */ +val FieldId.getter: MethodId + get() = MethodId(declaringClass, "get${name.replaceFirstChar { it.uppercase() } }", type, emptyList()) + +/** + * The default setter method for field (the one which is generated by Kotlin compiler) + */ +val FieldId.setter: MethodId + get() = MethodId(declaringClass, "set${name.replaceFirstChar { it.uppercase() } }", voidClassId, listOf(type)) From 3a93809ad3631e09949b41578462e1ed543b74d6 Mon Sep 17 00:00:00 2001 From: Ivan Volkov Date: Fri, 23 Sep 2022 18:37:18 +0300 Subject: [PATCH 2/9] Corrected behavior for static fields --- .../org/utbot/framework/codegen/model/util/ClassIdUtil.kt | 4 ++-- .../org/utbot/framework/codegen/model/util/FieldIdUtil.kt | 4 ++-- .../org/utbot/examples/annotations/ClassWithRefField.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/ClassIdUtil.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/ClassIdUtil.kt index d9cd8efd6e..38f2dbb71c 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/ClassIdUtil.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/ClassIdUtil.kt @@ -37,10 +37,10 @@ infix fun ClassId.isAccessibleFrom(packageName: String): Boolean { * Returns field of [this], such that [methodId] is a getter for it (or null if methodId doesn't represent a getter) */ internal fun ClassId.fieldThisIsGetterFor(methodId: MethodId): FieldId? = - allDeclaredFieldIds.firstOrNull { it.getter == methodId } + allDeclaredFieldIds.firstOrNull { !it.isStatic && it.getter == methodId } /** * Returns field of [this], such that [methodId] is a setter for it (or null if methodId doesn't represent a setter) */ internal fun ClassId.fieldThisIsSetterFor(methodId: MethodId): FieldId? = - allDeclaredFieldIds.firstOrNull { it.setter == methodId } \ No newline at end of file + allDeclaredFieldIds.firstOrNull { !it.isStatic && it.setter == methodId } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/FieldIdUtil.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/FieldIdUtil.kt index e2e324b531..3373609bda 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/FieldIdUtil.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/FieldIdUtil.kt @@ -15,7 +15,7 @@ import org.utbot.framework.plugin.api.util.voidClassId */ // TODO: change parameter from packageName: String to context: CgContext in ClassId.isAccessibleFrom and ExecutableId.isAccessibleFrom ? internal infix fun FieldId.isAccessibleFrom(context: CgContext): Boolean { - if (context.codegenLanguage == CodegenLanguage.KOTLIN) { + if (!isStatic && context.codegenLanguage == CodegenLanguage.KOTLIN) { // Here we call field accessible iff its getter is accessible, checks for setter are made in FieldId.canBeSetIn return declaringClass.allMethods.contains(getter) && getter.isAccessibleFrom(context.testClassPackageName) } @@ -31,7 +31,7 @@ internal infix fun FieldId.isAccessibleFrom(context: CgContext): Boolean { * Whether or not a field can be set without reflection */ internal fun FieldId.canBeSetIn(context: CgContext): Boolean { - if (context.codegenLanguage == CodegenLanguage.KOTLIN) { + if (!isStatic && context.codegenLanguage == CodegenLanguage.KOTLIN) { return declaringClass.allMethods.contains(setter) && setter.isAccessibleFrom(context.testClassPackageName) } return isAccessibleFrom(context) && !isFinal diff --git a/utbot-sample/src/main/java/org/utbot/examples/annotations/ClassWithRefField.java b/utbot-sample/src/main/java/org/utbot/examples/annotations/ClassWithRefField.java index 4995afe7c5..fa6422ec84 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/annotations/ClassWithRefField.java +++ b/utbot-sample/src/main/java/org/utbot/examples/annotations/ClassWithRefField.java @@ -21,7 +21,7 @@ public static void setStaticBoxedInt(@NotNull Integer staticBoxedInt) { } @SuppressWarnings("NullableProblems") - public Integer getBoxedInt() { + public @NotNull Integer getBoxedInt() { return boxedInt; } } From 4a46a78be7092bf35bb6c89362f7d9ac28c23158 Mon Sep 17 00:00:00 2001 From: Ivan Volkov Date: Fri, 23 Sep 2022 20:49:41 +0300 Subject: [PATCH 3/9] Always use safe call (?.) in Kotlin codegen --- .../utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt index 09459b5f8b..5b1c2fd282 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt @@ -202,8 +202,7 @@ internal class CgKotlinRenderer(context: CgRendererContext, printer: CgPrinter = } override fun renderAccess(caller: CgExpression) { - if (caller.type.isNullable) print("?") - print(".") + print("?.") } override fun visit(element: CgParameterDeclaration) { From 2143680e5669314a4ef5d4a1f54ccd65edfa0e9e Mon Sep 17 00:00:00 2001 From: Ivan Volkov Date: Fri, 23 Sep 2022 22:30:14 +0300 Subject: [PATCH 4/9] Fix calling wrong unary plus --- .../codegen/model/constructor/tree/CgVariableConstructor.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt index c7f7aea1de..f1020ed2ec 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt @@ -218,7 +218,11 @@ internal class CgVariableConstructor(val context: CgContext) : } is UtExecutableCallModel -> { val call = createCgExecutableCallFromUtExecutableCall(statementModel) - +replaceCgExecutableCallWithFieldAccessIfNeeded(call) + val callOrAccess = replaceCgExecutableCallWithFieldAccessIfNeeded(call) + if (callOrAccess is CgExecutableCall) + +callOrAccess + else + +callOrAccess } } } From c8739aa4986eb964b38d2782c702e0fb85661ecb Mon Sep 17 00:00:00 2001 From: Ivan Volkov Date: Sat, 24 Sep 2022 16:11:51 +0300 Subject: [PATCH 5/9] Fixed test crashing due to nullability problems --- .../utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt | 3 ++- .../org/utbot/framework/codegen/model/visitor/UtilMethods.kt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt index 5b1c2fd282..09459b5f8b 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt @@ -202,7 +202,8 @@ internal class CgKotlinRenderer(context: CgRendererContext, printer: CgPrinter = } override fun renderAccess(caller: CgExpression) { - print("?.") + if (caller.type.isNullable) print("?") + print(".") } override fun visit(element: CgParameterDeclaration) { diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt index 346b7463a1..3a9cb7b0ca 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt @@ -343,7 +343,7 @@ private fun createInstance(visibility: Visibility, language: CodegenLanguage): S } CodegenLanguage.KOTLIN -> { """ - ${visibility by language}fun createInstance(className: String): kotlin.Any? { + ${visibility by language}fun createInstance(className: String): kotlin.Any { val clazz: Class<*> = Class.forName(className) return Class.forName("sun.misc.Unsafe").getDeclaredMethod("allocateInstance", Class::class.java) .invoke(getUnsafeInstance(), clazz) From c8a84075be32645fd6770be6d5d5dab449810f2c Mon Sep 17 00:00:00 2001 From: Ivan Volkov Date: Sat, 24 Sep 2022 18:08:14 +0300 Subject: [PATCH 6/9] Treat java-accessible fields as accessible properties in Kotlin --- .../codegen/model/util/FieldIdUtil.kt | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/FieldIdUtil.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/FieldIdUtil.kt index 3373609bda..2cd320100e 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/FieldIdUtil.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/FieldIdUtil.kt @@ -15,9 +15,10 @@ import org.utbot.framework.plugin.api.util.voidClassId */ // TODO: change parameter from packageName: String to context: CgContext in ClassId.isAccessibleFrom and ExecutableId.isAccessibleFrom ? internal infix fun FieldId.isAccessibleFrom(context: CgContext): Boolean { - if (!isStatic && context.codegenLanguage == CodegenLanguage.KOTLIN) { + if (context.codegenLanguage == CodegenLanguage.KOTLIN) { // Here we call field accessible iff its getter is accessible, checks for setter are made in FieldId.canBeSetIn - return declaringClass.allMethods.contains(getter) && getter.isAccessibleFrom(context.testClassPackageName) + if (!isStatic && declaringClass.allMethods.contains(getter) && getter.isAccessibleFrom(context.testClassPackageName)) + return true } val packageName = context.testClassPackageName val isClassAccessible = declaringClass.isAccessibleFrom(packageName) @@ -31,10 +32,17 @@ internal infix fun FieldId.isAccessibleFrom(context: CgContext): Boolean { * Whether or not a field can be set without reflection */ internal fun FieldId.canBeSetIn(context: CgContext): Boolean { - if (!isStatic && context.codegenLanguage == CodegenLanguage.KOTLIN) { - return declaringClass.allMethods.contains(setter) && setter.isAccessibleFrom(context.testClassPackageName) + if (!isAccessibleFrom(context)) { + return false } - return isAccessibleFrom(context) && !isFinal + + if (context.codegenLanguage == CodegenLanguage.KOTLIN) { + if (!isStatic && declaringClass.allMethods.contains(setter) && setter.isAccessibleFrom(context.testClassPackageName)) { + return true + } + } + + return !isFinal } /** From 2245a0939d09f3d67f1d5c52e0fc4cad0bd9b99c Mon Sep 17 00:00:00 2001 From: Ivan Volkov Date: Sat, 24 Sep 2022 19:55:49 +0300 Subject: [PATCH 7/9] Fix false-positive canBeSetIn result for private fields with getters in Kotlin --- .../tree/CgCallableAccessManager.kt | 3 +- .../constructor/tree/CgFieldStateManager.kt | 5 +-- .../constructor/tree/CgMethodConstructor.kt | 12 +++---- .../constructor/tree/CgVariableConstructor.kt | 4 +-- .../constructor/tree/MockFrameworkManager.kt | 4 --- .../constructor/util/ConstructorUtils.kt | 2 -- .../codegen/model/util/FieldIdUtil.kt | 35 +++++++++++++------ .../annotations/ClassWithRefField.java | 2 +- 8 files changed, 38 insertions(+), 29 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt index ff3a3863da..6e06377f6d 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt @@ -33,6 +33,7 @@ import org.utbot.framework.codegen.model.tree.CgValue import org.utbot.framework.codegen.model.tree.CgVariable import org.utbot.framework.codegen.model.util.at import org.utbot.framework.codegen.model.util.isAccessibleFrom +import org.utbot.framework.codegen.model.util.canBeReadFrom import org.utbot.framework.codegen.model.util.nullLiteral import org.utbot.framework.codegen.model.util.resolve import org.utbot.framework.plugin.api.BuiltinConstructorId @@ -293,7 +294,7 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA private fun FieldId.accessSuitability(accessor: CgExpression?): FieldAccessorSuitability { // Check field accessibility. - if (!isAccessibleFrom(context)) { + if (!canBeReadFrom(context)) { return ReflectionOnly } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgFieldStateManager.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgFieldStateManager.kt index 64d2874999..5be6fd9c3e 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgFieldStateManager.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgFieldStateManager.kt @@ -18,6 +18,7 @@ import org.utbot.framework.codegen.model.tree.CgValue import org.utbot.framework.codegen.model.tree.CgVariable import org.utbot.framework.codegen.model.util.at import org.utbot.framework.codegen.model.util.isAccessibleFrom +import org.utbot.framework.codegen.model.util.canBeReadFrom import org.utbot.framework.codegen.model.util.stringLiteral import org.utbot.framework.fields.ArrayElementAccess import org.utbot.framework.fields.FieldAccess @@ -182,7 +183,7 @@ internal class CgFieldStateManagerImpl(val context: CgContext) for ((index, fieldPathElement) in path.withIndex()) { when (fieldPathElement) { is FieldAccess -> { - if (!fieldPathElement.field.isAccessibleFrom(context)) { + if (!fieldPathElement.field.canBeReadFrom(context)) { lastAccessibleIndex = index - 1 break } @@ -246,7 +247,7 @@ internal class CgFieldStateManagerImpl(val context: CgContext) private fun variableForStaticFieldState(owner: ClassId, fieldPath: FieldPath, customName: String?): CgVariable { val firstField = (fieldPath.elements.first() as FieldAccess).field - val firstAccessor = if (owner.isAccessibleFrom(testClassPackageName) && firstField.isAccessibleFrom(context)) { + val firstAccessor = if (owner.isAccessibleFrom(testClassPackageName) && firstField.canBeReadFrom(context)) { owner[firstField] } else { // TODO: there is a function getClassOf() for these purposes, but it is not accessible from here for now diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt index ec96677d7a..ba3b5b5a9e 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt @@ -66,10 +66,10 @@ import org.utbot.framework.codegen.model.tree.buildParameterizedTestDataProvider import org.utbot.framework.codegen.model.tree.buildTestMethod import org.utbot.framework.codegen.model.tree.convertDocToCg import org.utbot.framework.codegen.model.tree.toStatement -import org.utbot.framework.codegen.model.util.canBeSetIn +import org.utbot.framework.codegen.model.util.canBeSetFrom import org.utbot.framework.codegen.model.util.equalTo import org.utbot.framework.codegen.model.util.inc -import org.utbot.framework.codegen.model.util.isAccessibleFrom +import org.utbot.framework.codegen.model.util.canBeReadFrom import org.utbot.framework.codegen.model.util.length import org.utbot.framework.codegen.model.util.lessThan import org.utbot.framework.codegen.model.util.nullLiteral @@ -218,7 +218,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c val accessibleStaticFields = statics.accessibleFields() for ((field, _) in accessibleStaticFields) { val declaringClass = field.declaringClass - val fieldAccessible = field.isAccessibleFrom(context) + val fieldAccessible = field.canBeReadFrom(context) // prevValue is nullable if not accessible because of getStaticFieldValue(..) : Any? val prevValue = newVar( @@ -243,7 +243,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c val accessibleStaticFields = statics.accessibleFields() for ((field, model) in accessibleStaticFields) { val declaringClass = field.declaringClass - val fieldAccessible = field.canBeSetIn(context) + val fieldAccessible = field.canBeSetFrom(context) val fieldValue = if (isParametrized) { currentMethodParameters[CgParameterKind.Statics(model)] @@ -264,7 +264,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c private fun recoverStaticFields() { for ((field, prevValue) in prevStaticFieldValues.accessibleFields()) { - if (field.canBeSetIn(context)) { + if (field.canBeSetFrom(context)) { field.declaringClass[field] `=` prevValue } else { val declaringClass = getClassOf(field.declaringClass) @@ -1039,7 +1039,7 @@ internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by c private fun FieldId.getAccessExpression(variable: CgVariable): CgExpression = // Can directly access field only if it is declared in variable class (or in its ancestors) // and is accessible from current package - if (variable.type.hasField(this) && isAccessibleFrom(context)) { + if (variable.type.hasField(this) && canBeReadFrom(context)) { if (jField.isStatic) CgStaticFieldAccess(this) else CgFieldAccess(variable, this) } else { utilsClassId[getFieldValue](variable, this.declaringClass.name, this.name) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt index f1020ed2ec..dbcddb4729 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt @@ -28,7 +28,7 @@ import org.utbot.framework.codegen.model.tree.CgStaticFieldAccess import org.utbot.framework.codegen.model.tree.CgValue import org.utbot.framework.codegen.model.tree.CgVariable import org.utbot.framework.codegen.model.util.at -import org.utbot.framework.codegen.model.util.canBeSetIn +import org.utbot.framework.codegen.model.util.canBeSetFrom import org.utbot.framework.codegen.model.util.fieldThisIsGetterFor import org.utbot.framework.codegen.model.util.fieldThisIsSetterFor import org.utbot.framework.codegen.model.util.inc @@ -193,7 +193,7 @@ internal class CgVariableConstructor(val context: CgContext) : // byteBuffer is field of type ByteBuffer and upper line is incorrect val canFieldBeDirectlySetByVariableAndFieldTypeRestrictions = fieldFromVariableSpecifiedType != null && fieldFromVariableSpecifiedType.type.id == variableForField.type - if (canFieldBeDirectlySetByVariableAndFieldTypeRestrictions && fieldId.canBeSetIn(context)) { + if (canFieldBeDirectlySetByVariableAndFieldTypeRestrictions && fieldId.canBeSetFrom(context)) { // TODO: check if it is correct to use declaringClass of a field here val fieldAccess = if (field.isStatic) CgStaticFieldAccess(fieldId) else CgFieldAccess(obj, fieldId) fieldAccess `=` variableForField diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/MockFrameworkManager.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/MockFrameworkManager.kt index 25251a35fa..0dedf99d57 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/MockFrameworkManager.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/MockFrameworkManager.kt @@ -13,7 +13,6 @@ import org.utbot.framework.codegen.model.constructor.builtin.anyLong import org.utbot.framework.codegen.model.constructor.builtin.anyOfClass import org.utbot.framework.codegen.model.constructor.builtin.anyShort import org.utbot.framework.codegen.model.constructor.builtin.argumentMatchersClassId -import org.utbot.framework.codegen.model.constructor.builtin.forName import org.utbot.framework.codegen.model.constructor.builtin.mockMethodId import org.utbot.framework.codegen.model.constructor.builtin.mockedConstructionContextClassId import org.utbot.framework.codegen.model.constructor.builtin.mockitoClassId @@ -24,7 +23,6 @@ import org.utbot.framework.codegen.model.constructor.context.CgContextOwner import org.utbot.framework.codegen.model.constructor.util.CgComponents import org.utbot.framework.codegen.model.constructor.util.CgStatementConstructor import org.utbot.framework.codegen.model.constructor.util.CgStatementConstructorImpl -import org.utbot.framework.codegen.model.constructor.util.classCgClassId import org.utbot.framework.codegen.model.constructor.util.hasAmbiguousOverloadsOf import org.utbot.framework.codegen.model.tree.CgAnonymousFunction import org.utbot.framework.codegen.model.tree.CgAssignment @@ -33,7 +31,6 @@ import org.utbot.framework.codegen.model.tree.CgConstructorCall import org.utbot.framework.codegen.model.tree.CgDeclaration import org.utbot.framework.codegen.model.tree.CgExecutableCall import org.utbot.framework.codegen.model.tree.CgExpression -import org.utbot.framework.codegen.model.tree.CgGetJavaClass import org.utbot.framework.codegen.model.tree.CgLiteral import org.utbot.framework.codegen.model.tree.CgMethodCall import org.utbot.framework.codegen.model.tree.CgParameterDeclaration @@ -63,7 +60,6 @@ import org.utbot.framework.plugin.api.util.byteClassId import org.utbot.framework.plugin.api.util.charClassId import org.utbot.framework.plugin.api.util.doubleClassId import org.utbot.framework.plugin.api.util.floatClassId -import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.intClassId import org.utbot.framework.plugin.api.util.longClassId import org.utbot.framework.plugin.api.util.shortClassId diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt index a8736cb4fc..d25567d86b 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt @@ -5,8 +5,6 @@ import org.utbot.framework.codegen.StaticImport import org.utbot.framework.codegen.model.constructor.context.CgContextOwner import org.utbot.framework.codegen.model.tree.CgClassId import org.utbot.framework.codegen.model.tree.CgExpression -import org.utbot.framework.codegen.model.tree.CgGetClass -import org.utbot.framework.codegen.model.tree.CgGetJavaClass import org.utbot.framework.codegen.model.tree.CgTypeCast import org.utbot.framework.codegen.model.tree.CgValue import org.utbot.framework.codegen.model.tree.CgVariable diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/FieldIdUtil.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/FieldIdUtil.kt index 2cd320100e..542cea4b66 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/FieldIdUtil.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/FieldIdUtil.kt @@ -14,12 +14,12 @@ import org.utbot.framework.plugin.api.util.voidClassId * @param context context in which code is generated (it is needed because the method needs to know package and language) */ // TODO: change parameter from packageName: String to context: CgContext in ClassId.isAccessibleFrom and ExecutableId.isAccessibleFrom ? -internal infix fun FieldId.isAccessibleFrom(context: CgContext): Boolean { - if (context.codegenLanguage == CodegenLanguage.KOTLIN) { +private fun FieldId.isAccessibleFrom(context: CgContext): Boolean { + /*if (context.codegenLanguage == CodegenLanguage.KOTLIN) { // Here we call field accessible iff its getter is accessible, checks for setter are made in FieldId.canBeSetIn - if (!isStatic && declaringClass.allMethods.contains(getter) && getter.isAccessibleFrom(context.testClassPackageName)) + if (!isStatic && isAccessibleViaGetterFrom(context)) return true - } + }*/ val packageName = context.testClassPackageName val isClassAccessible = declaringClass.isAccessibleFrom(packageName) val isAccessibleByVisibility = isPublic || (declaringClass.packageName == packageName && (isPackagePrivate || isProtected)) @@ -28,21 +28,34 @@ internal infix fun FieldId.isAccessibleFrom(context: CgContext): Boolean { return isClassAccessible && isAccessibleFromPackageByModifiers } +private fun FieldId.canBeReadViaGetterFrom(context: CgContext): Boolean = + declaringClass.allMethods.contains(getter) && getter.isAccessibleFrom(context.testClassPackageName) + +internal infix fun FieldId.canBeReadFrom(context: CgContext): Boolean { + if (context.codegenLanguage == CodegenLanguage.KOTLIN) { + if (!isStatic && canBeReadViaGetterFrom(context)) + return true + } + + return isAccessibleFrom(context) +} + +private fun FieldId.canBeSetViaSetterFrom(context: CgContext): Boolean = + declaringClass.allMethods.contains(setter) && setter.isAccessibleFrom(context.testClassPackageName) + /** * Whether or not a field can be set without reflection */ -internal fun FieldId.canBeSetIn(context: CgContext): Boolean { - if (!isAccessibleFrom(context)) { - return false - } - +internal fun FieldId.canBeSetFrom(context: CgContext): Boolean { if (context.codegenLanguage == CodegenLanguage.KOTLIN) { - if (!isStatic && declaringClass.allMethods.contains(setter) && setter.isAccessibleFrom(context.testClassPackageName)) { + // Kotlin will allow direct write access if both getter and setter is defined (even if the field is final) + // TODO: add comment about final public and final private fields + if (!isAccessibleFrom(context) && !isStatic && canBeReadViaGetterFrom(context) && canBeSetViaSetterFrom(context)) { return true } } - return !isFinal + return isAccessibleFrom(context) && !isFinal } /** diff --git a/utbot-sample/src/main/java/org/utbot/examples/annotations/ClassWithRefField.java b/utbot-sample/src/main/java/org/utbot/examples/annotations/ClassWithRefField.java index fa6422ec84..4995afe7c5 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/annotations/ClassWithRefField.java +++ b/utbot-sample/src/main/java/org/utbot/examples/annotations/ClassWithRefField.java @@ -21,7 +21,7 @@ public static void setStaticBoxedInt(@NotNull Integer staticBoxedInt) { } @SuppressWarnings("NullableProblems") - public @NotNull Integer getBoxedInt() { + public Integer getBoxedInt() { return boxedInt; } } From 569e58140014cfed8ed3007f24cca9d3ac7243af Mon Sep 17 00:00:00 2001 From: Ivan Volkov Date: Mon, 26 Sep 2022 13:04:23 +0300 Subject: [PATCH 8/9] Add comments --- .../model/constructor/tree/CgVariableConstructor.kt | 6 +++--- .../framework/codegen/model/util/FieldIdUtil.kt | 13 ++++++------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt index dbcddb4729..82fd224e53 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt @@ -218,11 +218,11 @@ internal class CgVariableConstructor(val context: CgContext) : } is UtExecutableCallModel -> { val call = createCgExecutableCallFromUtExecutableCall(statementModel) - val callOrAccess = replaceCgExecutableCallWithFieldAccessIfNeeded(call) + val callOrAccess: CgStatement = replaceCgExecutableCallWithFieldAccessIfNeeded(call) if (callOrAccess is CgExecutableCall) - +callOrAccess + +callOrAccess // smart-cast => CgExecutableCall.unaryPlus() else - +callOrAccess + +callOrAccess // CgStatement.unaryPlus() } } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/FieldIdUtil.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/FieldIdUtil.kt index 542cea4b66..29309d46c6 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/FieldIdUtil.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/FieldIdUtil.kt @@ -15,11 +15,6 @@ import org.utbot.framework.plugin.api.util.voidClassId */ // TODO: change parameter from packageName: String to context: CgContext in ClassId.isAccessibleFrom and ExecutableId.isAccessibleFrom ? private fun FieldId.isAccessibleFrom(context: CgContext): Boolean { - /*if (context.codegenLanguage == CodegenLanguage.KOTLIN) { - // Here we call field accessible iff its getter is accessible, checks for setter are made in FieldId.canBeSetIn - if (!isStatic && isAccessibleViaGetterFrom(context)) - return true - }*/ val packageName = context.testClassPackageName val isClassAccessible = declaringClass.isAccessibleFrom(packageName) val isAccessibleByVisibility = isPublic || (declaringClass.packageName == packageName && (isPackagePrivate || isProtected)) @@ -31,8 +26,12 @@ private fun FieldId.isAccessibleFrom(context: CgContext): Boolean { private fun FieldId.canBeReadViaGetterFrom(context: CgContext): Boolean = declaringClass.allMethods.contains(getter) && getter.isAccessibleFrom(context.testClassPackageName) +/** + * Returns whether you can read field's value without reflection + */ internal infix fun FieldId.canBeReadFrom(context: CgContext): Boolean { if (context.codegenLanguage == CodegenLanguage.KOTLIN) { + // Kotlin will allow direct field access for non-static fields with accessible getter if (!isStatic && canBeReadViaGetterFrom(context)) return true } @@ -48,8 +47,8 @@ private fun FieldId.canBeSetViaSetterFrom(context: CgContext): Boolean = */ internal fun FieldId.canBeSetFrom(context: CgContext): Boolean { if (context.codegenLanguage == CodegenLanguage.KOTLIN) { - // Kotlin will allow direct write access if both getter and setter is defined (even if the field is final) - // TODO: add comment about final public and final private fields + // Kotlin will allow direct write access if both getter and setter is defined + // !isAccessibleFrom(context) is important here because above rule applies to final fields only if they are not accessible in Java terms if (!isAccessibleFrom(context) && !isStatic && canBeReadViaGetterFrom(context) && canBeSetViaSetterFrom(context)) { return true } From 38fb0d0240ca59b615c04d41eab78a5e19ba840b Mon Sep 17 00:00:00 2001 From: Ivan Volkov Date: Tue, 27 Sep 2022 18:21:35 +0300 Subject: [PATCH 9/9] Minor changes fixing issues found in review --- .../constructor/tree/CgVariableConstructor.kt | 51 ++++++++++++------- .../codegen/model/util/ClassIdUtil.kt | 8 +-- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt index 82fd224e53..b2d038536c 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt @@ -29,8 +29,8 @@ import org.utbot.framework.codegen.model.tree.CgValue import org.utbot.framework.codegen.model.tree.CgVariable import org.utbot.framework.codegen.model.util.at import org.utbot.framework.codegen.model.util.canBeSetFrom -import org.utbot.framework.codegen.model.util.fieldThisIsGetterFor -import org.utbot.framework.codegen.model.util.fieldThisIsSetterFor +import org.utbot.framework.codegen.model.util.fieldThatIsGotWith +import org.utbot.framework.codegen.model.util.fieldThatIsSetWith import org.utbot.framework.codegen.model.util.inc import org.utbot.framework.codegen.model.util.isAccessibleFrom import org.utbot.framework.codegen.model.util.lessThan @@ -218,11 +218,11 @@ internal class CgVariableConstructor(val context: CgContext) : } is UtExecutableCallModel -> { val call = createCgExecutableCallFromUtExecutableCall(statementModel) - val callOrAccess: CgStatement = replaceCgExecutableCallWithFieldAccessIfNeeded(call) - if (callOrAccess is CgExecutableCall) - +callOrAccess // smart-cast => CgExecutableCall.unaryPlus() + val equivalentFieldAccess = replaceCgExecutableCallWithFieldAccessIfNeeded(call) + if (equivalentFieldAccess != null) + +equivalentFieldAccess else - +callOrAccess // CgStatement.unaryPlus() + +call } } } @@ -246,7 +246,6 @@ internal class CgVariableConstructor(val context: CgContext) : val initExpr = if (isPrimitiveWrapperOrString(type)) { cgLiteralForWrapper(params) } else { - // TODO: if instantiation chain could be a setter call, we need to replace it in Kotlin createCgExecutableCallFromUtExecutableCall(executableCall) } newVar(type, model, baseName) { @@ -272,21 +271,35 @@ internal class CgVariableConstructor(val context: CgContext) : return cgCall } - private fun replaceCgExecutableCallWithFieldAccessIfNeeded(call: CgExecutableCall): CgStatement { - if (call !is CgMethodCall || context.codegenLanguage != CodegenLanguage.KOTLIN) - return call + /** + * If executable is getter/setter that should be syntactically replaced with field access + * (e.g., getter/setter generated by Kotlin in Kotlin code), this method returns [CgStatement] + * with which [call] should be replaced. + * + * Otherwise, returns null. + */ + private fun replaceCgExecutableCallWithFieldAccessIfNeeded(call: CgExecutableCall): CgStatement? { + when (context.codegenLanguage) { + CodegenLanguage.JAVA -> return null + CodegenLanguage.KOTLIN -> { + if (call !is CgMethodCall) + return null - val caller = call.caller ?: return call + val caller = call.caller ?: return null - caller.type.fieldThisIsSetterFor(call.executableId)?.let { - return CgAssignment(caller[it], call.arguments.single()) - } - caller.type.fieldThisIsGetterFor(call.executableId)?.let { - require(call.arguments.isEmpty()) - return caller[it] - } + caller.type.fieldThatIsSetWith(call.executableId)?.let { + return CgAssignment(caller[it], call.arguments.single()) + } + caller.type.fieldThatIsGotWith(call.executableId)?.let { + require(call.arguments.isEmpty()) { + "Method $call was detected as getter for $it, but its arguments list isn't empty" + } + return caller[it] + } - return call + return null + } + } } /** diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/ClassIdUtil.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/ClassIdUtil.kt index 38f2dbb71c..a215965853 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/ClassIdUtil.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/ClassIdUtil.kt @@ -36,11 +36,11 @@ infix fun ClassId.isAccessibleFrom(packageName: String): Boolean { /** * Returns field of [this], such that [methodId] is a getter for it (or null if methodId doesn't represent a getter) */ -internal fun ClassId.fieldThisIsGetterFor(methodId: MethodId): FieldId? = - allDeclaredFieldIds.firstOrNull { !it.isStatic && it.getter == methodId } +internal fun ClassId.fieldThatIsGotWith(methodId: MethodId): FieldId? = + allDeclaredFieldIds.singleOrNull { !it.isStatic && it.getter == methodId } /** * Returns field of [this], such that [methodId] is a setter for it (or null if methodId doesn't represent a setter) */ -internal fun ClassId.fieldThisIsSetterFor(methodId: MethodId): FieldId? = - allDeclaredFieldIds.firstOrNull { !it.isStatic && it.setter == methodId } \ No newline at end of file +internal fun ClassId.fieldThatIsSetWith(methodId: MethodId): FieldId? = + allDeclaredFieldIds.singleOrNull { !it.isStatic && it.setter == methodId } \ No newline at end of file