@@ -41,6 +41,7 @@ import org.utbot.framework.codegen.model.util.resolve
41
41
import org.utbot.framework.plugin.api.BuiltinClassId
42
42
import org.utbot.framework.plugin.api.BuiltinMethodId
43
43
import org.utbot.framework.plugin.api.ClassId
44
+ import org.utbot.framework.plugin.api.CodegenLanguage
44
45
import org.utbot.framework.plugin.api.ConstructorId
45
46
import org.utbot.framework.plugin.api.ExecutableId
46
47
import org.utbot.framework.plugin.api.FieldId
@@ -132,9 +133,7 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA
132
133
}
133
134
}
134
135
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)
138
137
139
138
private fun newMethodCall (methodId : MethodId ) {
140
139
if (isUtil(methodId)) requiredUtilMethods + = methodId
@@ -172,15 +171,14 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA
172
171
}
173
172
}
174
173
175
- private infix fun CgExpression.canBeReceiverOf (executable : MethodId ): Boolean {
176
- return when {
174
+ private infix fun CgExpression.canBeReceiverOf (executable : MethodId ): Boolean =
175
+ when {
177
176
// method of the current test class can be called on its 'this' instance
178
177
currentTestClass == executable.classId && this isThisInstanceOf currentTestClass -> true
179
178
// method of a class can be called on an object of this class or any of its subtypes
180
179
this .type isSubtypeOf executable.classId -> true
181
180
else -> false
182
181
}
183
- }
184
182
185
183
private infix fun CgExpression.canBeArgOf (type : ClassId ): Boolean {
186
184
// 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
257
255
* @return true if a method can be called with the given arguments without reflection
258
256
*/
259
257
private fun MethodId.canBeCalledWith (caller : CgExpression ? , args : List <CgExpression >): Boolean {
260
- // check method accessibility
258
+ // Check method accessibility.
261
259
if (! isAccessibleFrom(testClassPackageName)) {
262
260
return false
263
261
}
264
262
265
- // check arguments suitability
263
+ // Check arguments suitability.
266
264
if (! (args canBeArgsOf this )) {
267
265
return false
268
266
}
269
267
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 ) {
273
270
return true
274
271
}
275
272
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
279
280
}
280
281
281
282
requireNotNull(caller) { " Method must have a caller, unless it is the method of the current test class or a static method" }
282
283
return caller canBeReceiverOf this
283
284
}
284
285
285
286
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 ) {
289
294
return Suitable
290
295
}
291
296
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
+
292
301
// if field is declared in the current test class
293
302
if (this .declaringClass == currentTestClass) {
294
303
return when {
@@ -317,53 +326,106 @@ internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableA
317
326
val accessorType = accessor.type
318
327
319
328
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.
323
329
return Suitable
324
330
}
325
331
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
340
345
}
341
346
}
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
344
356
}
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
349
357
}
350
358
351
359
/* *
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.
353
407
*/
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
+ }
358
418
}
359
419
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
+ }
365
426
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()
367
429
}
368
430
369
431
/* *
0 commit comments