Skip to content

Commit 3503382

Browse files
committed
Fix field access check
1 parent b79e776 commit 3503382

File tree

1 file changed

+113
-51
lines changed

1 file changed

+113
-51
lines changed

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

Lines changed: 113 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import org.utbot.framework.codegen.model.util.resolve
4141
import org.utbot.framework.plugin.api.BuiltinClassId
4242
import org.utbot.framework.plugin.api.BuiltinMethodId
4343
import org.utbot.framework.plugin.api.ClassId
44+
import org.utbot.framework.plugin.api.CodegenLanguage
4445
import org.utbot.framework.plugin.api.ConstructorId
4546
import org.utbot.framework.plugin.api.ExecutableId
4647
import org.utbot.framework.plugin.api.FieldId
@@ -132,9 +133,7 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA
132133
}
133134
}
134135

135-
override operator fun ClassId.get(fieldId: FieldId): CgStaticFieldAccess {
136-
return CgStaticFieldAccess(fieldId)
137-
}
136+
override operator fun ClassId.get(fieldId: FieldId): CgStaticFieldAccess = CgStaticFieldAccess(fieldId)
138137

139138
private fun newMethodCall(methodId: MethodId) {
140139
if (isUtil(methodId)) requiredUtilMethods += methodId
@@ -172,15 +171,14 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA
172171
}
173172
}
174173

175-
private infix fun CgExpression.canBeReceiverOf(executable: MethodId): Boolean {
176-
return when {
174+
private infix fun CgExpression.canBeReceiverOf(executable: MethodId): Boolean =
175+
when {
177176
// method of the current test class can be called on its 'this' instance
178177
currentTestClass == executable.classId && this isThisInstanceOf currentTestClass -> true
179178
// method of a class can be called on an object of this class or any of its subtypes
180179
this.type isSubtypeOf executable.classId -> true
181180
else -> false
182181
}
183-
}
184182

185183
private infix fun CgExpression.canBeArgOf(type: ClassId): Boolean {
186184
// TODO: SAT-1210 support generics so that we wouldn't need to check specific cases such as this one
@@ -257,38 +255,49 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA
257255
* @return true if a method can be called with the given arguments without reflection
258256
*/
259257
private fun MethodId.canBeCalledWith(caller: CgExpression?, args: List<CgExpression>): Boolean {
260-
// check method accessibility
258+
// Check method accessibility.
261259
if (!isAccessibleFrom(testClassPackageName)) {
262260
return false
263261
}
264262

265-
// check arguments suitability
263+
// Check arguments suitability.
266264
if (!(args canBeArgsOf this)) {
267265
return false
268266
}
269267

270-
// If method is static, then it must not have a caller.
271-
if (this.isStatic) {
272-
require(caller == null) { "Caller expression of a static method call must be null" }
268+
// If method is static, then it may not have a caller.
269+
if (this.isStatic && caller == null) {
273270
return true
274271
}
275272

276-
// If method is from current test class, then it may or may not have a caller
277-
if (this.classId == currentTestClass) {
278-
return caller?.canBeReceiverOf(this) ?: true
273+
if (this.isStatic && caller != null && codegenLanguage == CodegenLanguage.KOTLIN) {
274+
error("In Kotlin, unlike Java, static methods cannot be called on an object")
275+
}
276+
277+
// If method is from current test class, then it may not have a caller.
278+
if (this.classId == currentTestClass && caller == null) {
279+
return true
279280
}
280281

281282
requireNotNull(caller) { "Method must have a caller, unless it is the method of the current test class or a static method" }
282283
return caller canBeReceiverOf this
283284
}
284285

285286
private fun FieldId.accessSuitability(accessor: CgExpression?): FieldAccessorSuitability {
286-
// if field is static
287-
if (this.isStatic) {
288-
require(accessor == null) { "Accessor expression of a static field access must be null" }
287+
// Check field accessibility.
288+
if (!isAccessibleFrom(testClassPackageName)) {
289+
return ReflectionOnly
290+
}
291+
292+
// If field is static, then it may not have an accessor.
293+
if (this.isStatic && accessor == null) {
289294
return Suitable
290295
}
291296

297+
if (this.isStatic && accessor != null && codegenLanguage == CodegenLanguage.KOTLIN) {
298+
error("In Kotlin, unlike Java, static fields cannot be accessed by an object")
299+
}
300+
292301
// if field is declared in the current test class
293302
if (this.declaringClass == currentTestClass) {
294303
return when {
@@ -317,53 +326,106 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA
317326
val accessorType = accessor.type
318327

319328
if (fieldDeclaringClassType is BuiltinClassId || accessorType is BuiltinClassId) {
320-
// The rest of the logic of this method processes hidden fields.
321-
// We cannot check the fields of builtin classes, so we assume that we work correctly with them,
322-
// because they are well known library classes (e.g. Mockito) that are unlikely to have hidden fields.
323329
return Suitable
324330
}
325331

326-
val fieldName = this.name
327-
328-
if (accessorType isSubtypeOf fieldDeclaringClassType || fieldDeclaringClassType isSubtypeOf accessorType) {
329-
if (fieldName.isNameOfFieldsInBothClasses(accessorType, fieldDeclaringClassType)) {
330-
// classes are in an inheritance relation, and they both have a field with the same name,
331-
// which means that field shadowing is found
332-
return when {
333-
// if we can access the declaring class of field,
334-
// then we can use a type cast the accessor to it
335-
fieldDeclaringClassType isAccessibleFrom testClassPackageName -> {
336-
RequiresTypeCast(fieldDeclaringClassType)
337-
}
338-
// otherwise, we can only use reflection
339-
else -> ReflectionOnly
332+
// The rest of the logic of this method processes hidden fields.
333+
// We cannot check the fields of builtin classes, so we assume that we work correctly with them,
334+
// because they are well known library classes (e.g. Mockito) that are unlikely to have hidden fields.
335+
336+
return when {
337+
accessorType isSubtypeOf fieldDeclaringClassType -> {
338+
if (this isFieldHiddenIn accessorType.jClass) {
339+
// We know that declaring class of field is accessible,
340+
// because it was checked in `isAccessibleFrom` call at the start of this method,
341+
// so we can safely do a type cast.
342+
RequiresTypeCast(fieldDeclaringClassType)
343+
} else {
344+
Suitable
340345
}
341346
}
342-
// if no field shadowing is found, we can just access the field directly
343-
return Suitable
347+
fieldDeclaringClassType isSubtypeOf accessorType -> {
348+
// We know that declaring class of field is accessible,
349+
// because it was checked in `isAccessibleFrom` call at the start of this method,
350+
// so we can safely do a type cast.
351+
RequiresTypeCast(fieldDeclaringClassType)
352+
}
353+
// Accessor type is not subtype or supertype of the field's declaring class.
354+
// So the only remaining option to access the field is to use reflection.
355+
else -> ReflectionOnly
344356
}
345-
346-
// Accessor type is not subtype or supertype of the field's declaring class.
347-
// So the only remaining option to access the field is to use reflection.
348-
return ReflectionOnly
349357
}
350358

351359
/**
352-
* Determine if each of the classes [left] and [right] has their own field with name specified by the [String] receiver.
360+
* Check if the field represented by @receiver is hidden when accessed from an instance of [subclass].
361+
*
362+
* For example, given classes:
363+
* ```
364+
* class A { int x; }
365+
* class B extends A { int x; }
366+
* ```
367+
* we can say that field `x` of class `A` is hidden when we are dealing with instances of a subclass `B`:
368+
* ```
369+
* B b = new B();
370+
* b.x = 10;
371+
* ```
372+
* There is no way to access field `x` of class `A` from variable `b` without using type casts or reflection.
373+
*
374+
* So the result of [isFieldHiddenIn] for field `x` of class `A` and a subclass `B` would be `true`.
375+
*
376+
* **NOTE** that there can be more complicated cases. For example, interfaces can also have fields (they are always static).
377+
* Fields of an interface will be available to the classes and interfaces that inherit from it.
378+
* That means that when checking if a field is hidden we have to take **both** classes and interfaces into account.
379+
*
380+
* **However**, there is an important detail. We **do not** consider superclasses and superinterfaces
381+
* of the type where the field (represented by @receiver) was declared. Consider the following example:
382+
* ```
383+
* class A { int x; }
384+
* class B extends A { int x; }
385+
* class C extends B { }
386+
* ```
387+
* If we are checking if the field `x` of class `B` is hidden when accessed by an instance of class `C`,
388+
* then [isFieldHiddenIn] will return `false`, because the field `x` of class `A` does not stand in the way
389+
* and is itself hidden by `B.x`. That is why we **can** access `B.x` from a variable of type `C`.
390+
*
391+
* Lastly, another **important** example:
392+
* ```
393+
* class A { int x; }
394+
* interface I1 { int x = 10; }
395+
* class B extends A implements I1 { }
396+
* ```
397+
* Unlike previous examples, here we have class `B` which has different "branches" of supertypes.
398+
* On the one hand we have superclass `A` and on the other we have interface `I1`.
399+
* `A` and `I1` do not have any relationship with each other, but `B` **can** access fields `x` from both of them.
400+
* However, it **must** be done with a type cast (to `A` or `I1`), because otherwise accessing field `x` will
401+
* be ambiguous. Method [isFieldHiddenIn] **does consider** such cases as well.
402+
*
403+
* **These examples show** that when checking if a field is hidden we **must** consider all supertypes
404+
* in all "branches" **up to** the type where the field is declared (**exclusively**).
405+
* Then we check if any of these types has a field with the same name.
406+
* If such field is found, then the field is hidden, otherwise - not.
353407
*/
354-
private fun String.isNameOfFieldsInBothClasses(left: ClassId, right: ClassId): Boolean {
355-
// make sure that non-builtin class ids are passed to this method
356-
require(left !is BuiltinClassId && right !is BuiltinClassId) {
357-
"The given class ids must not be builtin, because this method works with their jClass"
408+
private infix fun FieldId.isFieldHiddenIn(subclass: Class<*>): Boolean {
409+
// supertypes (classes and interfaces) from subclass (inclusive) up to superclass (exclusive)
410+
val supertypes = sequence {
411+
var types = generateSequence(subclass) { it.superclass }
412+
.takeWhile { it != declaringClass.jClass }
413+
.toList()
414+
while (types.isNotEmpty()) {
415+
yieldAll(types)
416+
types = types.flatMap { it.interfaces.toList() }
417+
}
358418
}
359419

360-
val leftClass = left.jClass
361-
val rightClass = right.jClass
362-
363-
val leftField = leftClass.declaredFields.find { it.name == this }
364-
val rightField = rightClass.declaredFields.find { it.name == this }
420+
// check if any of the collected supertypes declare a field with the same name
421+
val fieldHidingTypes = supertypes.toList()
422+
.filter { type ->
423+
val fieldNames = type.declaredFields.map { it.name }
424+
this.name in fieldNames
425+
}
365426

366-
return leftField != null && rightField != null
427+
// if we found at least one type that hides the field, then the field is hidden
428+
return fieldHidingTypes.isNotEmpty()
367429
}
368430

369431
/**

0 commit comments

Comments
 (0)