|
| 1 | +/** |
| 2 | + * Finds method reference expressions which might by accident refer to collection constructors |
| 3 | + * or factory methods which take an `int` as capacity argument. The method reference expression |
| 4 | + * might have been intended to just create a new collection, and the argument is accidentally |
| 5 | + * misinterpreted as capacity. |
| 6 | + * |
| 7 | + * If usage of the referenced constructor or method with capacity parameter is intended, the code |
| 8 | + * should be rewritten as lambda expression to make this explicit. For example: |
| 9 | + * ```java |
| 10 | + * Map<Integer, List<String>> map = ...; |
| 11 | + * // Actually calls `new ArrayList<>(capacity)` |
| 12 | + * map.computeIfAbsent(1, ArrayList::new); |
| 13 | + * |
| 14 | + * // If that is intended, it should be written like this: |
| 15 | + * map.computeIfAbsent(1, capacity -> new ArrayList<>(capacity)); |
| 16 | + * // Or otherwise the argument should be ignored: |
| 17 | + * map.computeIfAbsent(1, key -> new ArrayList<>()); |
| 18 | + * ``` |
| 19 | + * |
| 20 | + * Based on [Java Collections Puzzlers by Maurice Naftalin And José Paumard](https://youtu.be/w6hhjg_gt_M?t=1873). |
| 21 | + * |
| 22 | + * @kind problem |
| 23 | + */ |
| 24 | + |
| 25 | +import java |
| 26 | + |
| 27 | +from MethodAccess enclosingCall, MemberRefExpr methodRef, Callable referencedCallable |
| 28 | +where |
| 29 | + // To reduce false positives only consider method ref which is argument to an instance method call (e.g. `Map.computeIfAbsent`) |
| 30 | + enclosingCall.getAnArgument() = methodRef |
| 31 | + and not enclosingCall.getMethod().isStatic() |
| 32 | + and methodRef.getReferencedCallable() = referencedCallable |
| 33 | + and ( |
| 34 | + referencedCallable instanceof Constructor |
| 35 | + // By convention Collection and Map implementations often have constructor which takes an `int` initial capacity |
| 36 | + and referencedCallable.getDeclaringType().getSourceDeclaration().getASourceSupertype*().hasQualifiedName("java.util", ["Collection", "Map"]) |
| 37 | + or |
| 38 | + // Or static factory methods which create collection or map with capacity |
| 39 | + // TODO: Maybe remove this again if it causes too many false positives; though from the method names alone, it is |
| 40 | + // not immediately obvious that they take a capacity argument |
| 41 | + referencedCallable.(Method).isStatic() |
| 42 | + and exists(string type, string methodName | |
| 43 | + referencedCallable.getDeclaringType().getQualifiedName() = type |
| 44 | + and referencedCallable.hasName(methodName) |
| 45 | + | |
| 46 | + type = "java.util.concurrent.ConcurrentHashMap" and methodName = "newKeySet" |
| 47 | + or type = "java.util.HashMap" and methodName = "newHashMap" |
| 48 | + or type = "java.util.HashSet" and methodName = "newHashSet" |
| 49 | + or type = "java.util.WeakHashMap" and methodName = "newWeakHashMap" |
| 50 | + or type = "java.util.LinkedHashMap" and methodName = "newLinkedHashMap" |
| 51 | + or type = "java.util.LinkedHashSet" and methodName = "newLinkedHashSet" |
| 52 | + ) |
| 53 | + ) |
| 54 | + and referencedCallable.getNumberOfParameters() = 1 |
| 55 | + and referencedCallable.getParameterType(0).(PrimitiveType).hasName("int") |
| 56 | +select methodRef, "Might by accident create collection with certain capacity" |
0 commit comments