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..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(testClassPackageName)) { + 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 59e4c9561e..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(testClassPackageName)) { + 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(testClassPackageName)) { + 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 367c68a8f5..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(testClassPackageName) + 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(testClassPackageName) + 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(testClassPackageName)) { + 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(testClassPackageName)) { + 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 f8b453b55a..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 @@ -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.canBeSetFrom +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 @@ -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.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 @@ -212,7 +217,12 @@ internal class CgVariableConstructor(val context: CgContext) : instance[statementModel.fieldId] `=` declareOrGet(statementModel.fieldModel) } is UtExecutableCallModel -> { - +createCgExecutableCallFromUtExecutableCall(statementModel) + val call = createCgExecutableCallFromUtExecutableCall(statementModel) + val equivalentFieldAccess = replaceCgExecutableCallWithFieldAccessIfNeeded(call) + if (equivalentFieldAccess != null) + +equivalentFieldAccess + else + +call } } } @@ -261,6 +271,37 @@ internal class CgVariableConstructor(val context: CgContext) : return cgCall } + /** + * 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 null + + 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 null + } + } + } + /** * 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/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/ClassIdUtil.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/ClassIdUtil.kt index 13eab73607..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 @@ -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.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.fieldThatIsSetWith(methodId: MethodId): FieldId? = + allDeclaredFieldIds.singleOrNull { !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 ad54a9b5b9..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 @@ -1,15 +1,21 @@ 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 ? +private fun FieldId.isAccessibleFrom(context: CgContext): Boolean { + val packageName = context.testClassPackageName val isClassAccessible = declaringClass.isAccessibleFrom(packageName) val isAccessibleByVisibility = isPublic || (declaringClass.packageName == packageName && (isPackagePrivate || isProtected)) val isAccessibleFromPackageByModifiers = isAccessibleByVisibility && !isSynthetic @@ -17,7 +23,48 @@ infix fun FieldId.isAccessibleFrom(packageName: String): Boolean { return isClassAccessible && isAccessibleFromPackageByModifiers } +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 + } + + 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 */ -fun FieldId.canBeSetIn(packageName: String): Boolean = isAccessibleFrom(packageName) && !isFinal +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 + // !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 + } + } + + 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)) 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)