-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Expand file tree
/
Copy pathImplicitReturn.ql
More file actions
92 lines (84 loc) · 3.25 KB
/
ImplicitReturn.ql
File metadata and controls
92 lines (84 loc) · 3.25 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
/**
* @name Not all control paths return a value
* @description Functions where some execution paths return an explicit value while others
* "fall off" the end of the function and return 'undefined' are hard to maintain and use.
* @kind problem
* @problem.severity recommendation
* @id js/implicit-return
* @tags quality
* reliability
* correctness
* readability
* @precision medium
*/
import javascript
import semmle.javascript.RestrictedLocations
/**
* Holds if `s` is a statement that terminates execution of the surrounding method,
* either by returning or throwing an exception. As a special case, we include calls
* to functions whose name contains `"throw"`.
*/
predicate isThrowOrReturn(Stmt s) {
s instanceof ReturnStmt or
s instanceof ThrowStmt or
s.(ExprStmt).getExpr().(CallExpr).getCalleeName().matches("%throw%")
}
/**
* A `return` statement with an operand (that is, not just `return;`).
*/
class ValueReturn extends ReturnStmt {
ValueReturn() { exists(this.getExpr()) }
}
/** Gets the lexically first explicit return statement in function `f`. */
ValueReturn getFirstExplicitReturn(Function f) {
result =
min(ValueReturn ret |
ret.getContainer() = f
|
ret order by ret.getLocation().getStartLine(), ret.getLocation().getStartColumn()
)
}
/** Gets the number of return statements in function `f`, assuming there is at least one. */
int numRet(Function f) { result = strictcount(ReturnStmt ret | ret.getContainer() = f) }
/**
* Holds if `f` is a dual-use constructor, that is, is a function that is meant to be invoked
* with `new` only, but guards against the case where it is invoked as a plain function.
*
* We consider a function to be a dual-use constructor if
*
* 1. it has a single `return` statement
* 2. that `return` statement returns a `new` expression
* 3. and the `new` expression instantiates the function itself
*/
predicate isDualUseConstructor(Function f) {
numRet(f) = 1 and
exists(ReturnStmt ret, DataFlow::NewNode new | ret.getContainer() = f |
new.asExpr() = ret.getExpr().getUnderlyingValue() and
new.getACallee() = f
)
}
/**
* Gets a fall-through statement in function `f`, that is, a statement that is reachable,
* does not have a successor statement inside the function, isn't a throw or a return,
* and isn't contained in a `finally` block.
*/
Stmt getAFallThroughStmt(Function f) {
exists(ReachableBasicBlock bb, ControlFlowNode nd |
bb.getANode() = nd and
nd.isAFinalNode() and
f = bb.getContainer() and
(result = nd or result = nd.(Expr).getEnclosingStmt()) and
not isThrowOrReturn(result) and
not exists(TryStmt try | result.getParentStmt+() = try.getFinally())
)
}
from Function f, Stmt fallthrough
where
fallthrough = getAFallThroughStmt(f) and
// no control path ends with an implicit return statement of the form `return;`
not exists(ReturnStmt ret | ret.getContainer() = f and not exists(ret.getExpr())) and
// f doesn't look like a dual-use constructor (which otherwise would trigger a violation)
not isDualUseConstructor(f)
select fallthrough.(FirstLineOf),
"$@ may implicitly return 'undefined' here, while $@ an explicit value is returned.", f,
capitalize(f.describe()), getFirstExplicitReturn(f), "elsewhere"