forked from github/codeql
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathUnreachableMethodOverloads.ql
More file actions
137 lines (125 loc) · 5.11 KB
/
UnreachableMethodOverloads.ql
File metadata and controls
137 lines (125 loc) · 5.11 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
130
131
132
133
134
135
136
137
/**
* @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
// same this parameter (if exists)
(
not exists(method.getBody().getThisTypeAnnotation()) and
not exists(other.getBody().getThisTypeAnnotation())
or
method.getBody().getThisTypeAnnotation().getType() =
other.getBody().getThisTypeAnnotation().getType()
) and
// The types are compared in matchingCallSignature. This is a consistency 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"