-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathUncheckedReturnValue.ql
More file actions
123 lines (113 loc) · 3.81 KB
/
UncheckedReturnValue.ql
File metadata and controls
123 lines (113 loc) · 3.81 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
/**
* @name Unchecked return value
* @description If most of the calls to a method use the return value
* of that method, the calls that do not check the return value may be mistakes.
* @kind problem
* @problem.severity warning
* @precision low
* @id cs/unchecked-return-value
* @tags reliability
* correctness
* external/cwe/cwe-252
* statistical
* non-attributable
*/
import csharp
import semmle.code.csharp.frameworks.system.IO
import semmle.code.csharp.Chaining
/** Holds if `m` is a method whose return value should always be checked. */
predicate important(Method m) {
exists(Method read | read = any(SystemIOStreamClass c).getReadMethod() |
m = read.getAnOverrider*()
)
or
exists(Method readByte | readByte = any(SystemIOStreamClass c).getReadByteMethod() |
m = readByte.getAnOverrider*()
)
// add more ...
}
/** Holds if the return type of `m` is an instantiated type parameter from `m`. */
predicate methodHasGenericReturnType(ConstructedMethod cm) {
exists(UnboundGenericMethod ugm |
ugm = cm.getUnboundGeneric() and
ugm.getReturnType() = ugm.getATypeParameter()
)
}
/** Holds if `m` is a method whose return value should be checked because most calls to `m` do. */
// statistically dubious:
predicate dubious(Method m, int percentage) {
not important(m) and
// Suppress on Void methods
not m.getReturnType() instanceof VoidType and
// Suppress on methods designed for chaining
not designedForChaining(m) and
exists(int used, int total, Method target |
target = m.getUnboundDeclaration() and
used =
count(MethodCall mc |
mc.getTarget().getUnboundDeclaration() = target and
not mc instanceof DiscardedMethodCall and
(methodHasGenericReturnType(m) implies m.getReturnType() = mc.getTarget().getReturnType())
) and
total =
count(MethodCall mc |
mc.getTarget().getUnboundDeclaration() = target and
(methodHasGenericReturnType(m) implies m.getReturnType() = mc.getTarget().getReturnType())
) and
used != total and
percentage = used * 100 / total and
percentage >= 90 and
chainedUses(m) * 100 / total <= 45 // no more than 45% of calls to this method are chained
)
}
int chainedUses(Method m) {
result =
count(MethodCall mc, MethodCall qual |
m = mc.getTarget() and
hasQualifierAndTarget(mc, qual, qual.getTarget())
)
}
predicate hasQualifierAndTarget(MethodCall mc, Expr qualifier, Method m) {
qualifier = mc.getQualifier() and
m = mc.getTarget()
}
/** Holds if `m` is a white-listed method where checking the return value is not required. */
predicate whitelist(Method m) {
m.hasName("TryGetValue")
or
m.hasName("TryParse")
or
exists(Namespace n | n = m.getDeclaringType().getNamespace().getParentNamespace*() |
n.getName().regexpMatch("(Fluent)?NHibernate") or
n.getName() = "Moq"
)
// add more ...
}
class DiscardedMethodCall extends MethodCall {
DiscardedMethodCall() {
this.getParent() instanceof ExprStmt
or
exists(Callable c |
this = c.getExpressionBody() and
not c.canReturn(this)
)
}
string query() {
exists(Method m |
m = this.getTarget() and
not whitelist(m) and
// Do not alert on "void wrapper methods", i.e., methods that are inserted
// to deliberately ignore the returned value
not this.getEnclosingCallable().getStatementBody().getNumberOfStmts() = 1
|
important(m) and result = "should always be checked"
or
exists(int percentage | dubious(m, percentage) |
result = percentage.toString() + "% of calls to this method have their result used"
)
)
}
}
from DiscardedMethodCall dmc, string message
where message = dmc.query()
select dmc, "Result of call to '" + dmc.getTarget().getName() + "' is ignored, but " + message + "."