-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathUnreachableMethodOverloads.ql
More file actions
129 lines (117 loc) · 4.82 KB
/
UnreachableMethodOverloads.ql
File metadata and controls
129 lines (117 loc) · 4.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/**
* @name Unreachable method overloads
* @description Having multiple overloads with the same parameter types in TypeScript
* makes all overloads except the first one unreachable, as the compiler
* always resolves calls to the textually first matching overload.
* @kind problem
* @problem.severity warning
* @id js/unreachable-method-overloads
* @precision high
* @tags correctness
* typescript
*/
import javascript
/**
* Gets the `i`th parameter from the method signature.
*/
SimpleParameter getParameter(MethodSignature sig, int i) { result = sig.getBody().getParameter(i) }
/**
* Gets a string-representation of the type-annotation from the `i`th parameter in the method signature.
*/
string getParameterTypeAnnotation(MethodSignature sig, int i) {
result = getParameter(sig, i).getTypeAnnotation().toString()
}
/**
* Gets the other overloads for an overloaded method signature.
*/
MethodSignature getOtherMatchingSignatures(MethodSignature sig) {
signaturesMatch(result, sig) and
result != sig
}
/**
* Gets the kind of the member-declaration. Either "static" or "instance".
*/
string getKind(MemberDeclaration m) {
if m.isStatic() then result = "static" else result = "instance"
}
/**
* A call-signature that originates from a MethodSignature in the AST.
*/
private class MethodCallSig extends CallSignatureType {
string name;
MethodCallSig() {
exists(MethodSignature sig |
this = sig.getBody().getCallSignature() and
name = sig.getName()
)
}
/**
* Gets the name of any member that has this signature.
*/
string getName() { result = name }
}
/**
* Holds if the two call signatures could be overloads of each other and have the same parameter types.
*/
predicate matchingCallSignature(MethodCallSig method, MethodCallSig other) {
method.getName() = other.getName() and
method.getNumOptionalParameter() = other.getNumOptionalParameter() and
method.getNumParameter() = other.getNumParameter() and
method.getNumRequiredParameter() = other.getNumRequiredParameter() and
// purposely not looking at number of type arguments.
method.getKind() = other.getKind() and
forall(int i | i in [0 .. -1 + method.getNumParameter()] |
method.getParameter(i) = other.getParameter(i) // This is sometimes imprecise, so it is still a good idea to compare type annotations.
) and
// shared type parameters are equal.
forall(int i |
i in [0 .. -1 +
min(int num | num = method.getNumTypeParameter() or num = other.getNumTypeParameter())]
|
method.getTypeParameterBound(i) = other.getTypeParameterBound(i)
)
}
/**
* Gets which overload index the MethodSignature has among the overloads of the same name.
*/
int getOverloadIndex(MethodSignature sig) {
sig.getDeclaringType().getMethodOverload(sig.getName(), result) = sig
}
/**
* Holds if the two method signatures are overloads of each other and have the same parameter types.
*/
predicate signaturesMatch(MethodSignature method, MethodSignature other) {
// declared in the same interface/class.
method.getDeclaringType() = other.getDeclaringType() and
// same static modifier.
getKind(method) = getKind(other) and
// same name.
method.getName() = other.getName() and
// same number of parameters.
method.getBody().getNumParameter() = other.getBody().getNumParameter() and
// The types are compared in matchingCallSignature. This is sanity-check that the textual representation of the type-annotations are somewhat similar.
forall(int i | i in [0 .. -1 + method.getBody().getNumParameter()] |
getParameterTypeAnnotation(method, i) = getParameterTypeAnnotation(other, i)
) and
matchingCallSignature(method.getBody().getCallSignature(), other.getBody().getCallSignature())
}
from ClassOrInterface decl, string name, MethodSignature previous, MethodSignature unreachable
where
previous = decl.getMethod(name) and
unreachable = getOtherMatchingSignatures(previous) and
// If the method is part of inheritance between classes/interfaces, then there can sometimes be reasons for having this pattern.
not exists(decl.getASuperTypeDeclaration().getMethod(name)) and
not exists(ClassOrInterface sub |
decl = sub.getASuperTypeDeclaration() and
exists(sub.getMethod(name))
) and
// If a later method overload has more type parameters, then that overload can be selected by explicitly declaring the type arguments at the callsite.
// This comparison removes those cases.
unreachable.getBody().getNumTypeParameter() <= previous.getBody().getNumTypeParameter() and
// We always select the first of the overloaded methods.
not exists(MethodSignature later | later = getOtherMatchingSignatures(previous) |
getOverloadIndex(later) < getOverloadIndex(previous)
)
select unreachable,
"This overload of " + name + "() is unreachable, the $@ overload will always be selected.",
previous, "previous"