Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 592cb18

Browse files
committed
add array callbacks to useOfReturnlessFunction query
1 parent 75bf339 commit 592cb18

3 files changed

Lines changed: 178 additions & 121 deletions

File tree

javascript/ql/src/Expressions/ExprHasNoEffect.ql

Lines changed: 1 addition & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -13,122 +13,9 @@
1313
*/
1414

1515
import javascript
16-
import DOMProperties
17-
import semmle.javascript.frameworks.xUnit
18-
import semmle.javascript.RestrictedLocations
1916
import ExprHasNoEffect
2017

21-
/**
22-
* Holds if `e` is of the form `x;` or `e.p;` and has a JSDoc comment containing a tag.
23-
* In that case, it is probably meant as a declaration and shouldn't be flagged by this query.
24-
*
25-
* This will still flag cases where the JSDoc comment contains no tag at all (and hence carries
26-
* no semantic information), and expression statements with an ordinary (non-JSDoc) comment
27-
* attached to them.
28-
*/
29-
predicate isDeclaration(Expr e) {
30-
(e instanceof VarAccess or e instanceof PropAccess) and
31-
exists(e.getParent().(ExprStmt).getDocumentation().getATag())
32-
}
33-
34-
/**
35-
* Holds if there exists a getter for a property called `name` anywhere in the program.
36-
*/
37-
predicate isGetterProperty(string name) {
38-
// there is a call of the form `Object.defineProperty(..., name, descriptor)` ...
39-
exists(CallToObjectDefineProperty defProp | name = defProp.getPropertyName() |
40-
// ... where `descriptor` defines a getter
41-
defProp.hasPropertyAttributeWrite("get", _)
42-
or
43-
// ... where `descriptor` may define a getter
44-
exists(DataFlow::SourceNode descriptor | descriptor.flowsTo(defProp.getPropertyDescriptor()) |
45-
descriptor.isIncomplete(_)
46-
or
47-
// minimal escape analysis for the descriptor
48-
exists(DataFlow::InvokeNode invk |
49-
not invk = defProp and
50-
descriptor.flowsTo(invk.getAnArgument())
51-
)
52-
)
53-
)
54-
or
55-
// there is an object expression with a getter property `name`
56-
exists(ObjectExpr obj | obj.getPropertyByName(name) instanceof PropertyGetter)
57-
}
58-
59-
/**
60-
* A property access that may invoke a getter.
61-
*/
62-
class GetterPropertyAccess extends PropAccess {
63-
override predicate isImpure() { isGetterProperty(getPropertyName()) }
64-
}
65-
66-
/**
67-
* Holds if `c` is an indirect eval call of the form `(dummy, eval)(...)`, where
68-
* `dummy` is some expression whose value is discarded, and which simply
69-
* exists to prevent the call from being interpreted as a direct eval.
70-
*/
71-
predicate isIndirectEval(CallExpr c, Expr dummy) {
72-
exists(SeqExpr seq | seq = c.getCallee().stripParens() |
73-
dummy = seq.getOperand(0) and
74-
seq.getOperand(1).(GlobalVarAccess).getName() = "eval" and
75-
seq.getNumOperands() = 2
76-
)
77-
}
78-
79-
/**
80-
* Holds if `c` is a call of the form `(dummy, e[p])(...)`, where `dummy` is
81-
* some expression whose value is discarded, and which simply exists
82-
* to prevent the call from being interpreted as a method call.
83-
*/
84-
predicate isReceiverSuppressingCall(CallExpr c, Expr dummy, PropAccess callee) {
85-
exists(SeqExpr seq | seq = c.getCallee().stripParens() |
86-
dummy = seq.getOperand(0) and
87-
seq.getOperand(1) = callee and
88-
seq.getNumOperands() = 2
89-
)
90-
}
91-
92-
/**
93-
* Holds if evaluating `e` has no side effects (except potentially allocating
94-
* and initializing a new object).
95-
*
96-
* For calls, we do not check whether their arguments have any side effects:
97-
* even if they do, the call itself is useless and should be flagged by this
98-
* query.
99-
*/
100-
predicate noSideEffects(Expr e) {
101-
e.isPure()
102-
or
103-
// `new Error(...)`, `new SyntaxError(...)`, etc.
104-
forex(Function f | f = e.flow().(DataFlow::NewNode).getACallee() |
105-
f.(ExternalType).getASupertype*().getName() = "Error"
106-
)
107-
}
10818

10919
from Expr e
110-
where
111-
noSideEffects(e) and
112-
inVoidContext(e) and
113-
// disregard pure expressions wrapped in a void(...)
114-
not e instanceof VoidExpr and
115-
// filter out directives (unknown directives are handled by UnknownDirective.ql)
116-
not exists(Directive d | e = d.getExpr()) and
117-
// or about externs
118-
not e.inExternsFile() and
119-
// don't complain about declarations
120-
not isDeclaration(e) and
121-
// exclude DOM properties, which sometimes have magical auto-update properties
122-
not isDOMProperty(e.(PropAccess).getPropertyName()) and
123-
// exclude xUnit.js annotations
124-
not e instanceof XUnitAnnotation and
125-
// exclude common patterns that are most likely intentional
126-
not isIndirectEval(_, e) and
127-
not isReceiverSuppressingCall(_, e, _) and
128-
// exclude anonymous function expressions as statements; these can only arise
129-
// from a syntax error we already flag
130-
not exists(FunctionExpr fe, ExprStmt es | fe = e |
131-
fe = es.getExpr() and
132-
not exists(fe.getName())
133-
)
20+
where hasNoEffect(e)
13421
select e.(FirstLineOf), "This expression has no effect."

javascript/ql/src/Expressions/ExprHasNoEffect.qll

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
*/
44

55
import javascript
6+
import DOMProperties
7+
import semmle.javascript.frameworks.xUnit
8+
import semmle.javascript.RestrictedLocations
69

710
/**
811
* Holds if `e` appears in a syntactic context where its value is discarded.
@@ -37,3 +40,121 @@ predicate inVoidContext(Expr e) {
3740
or
3841
exists(LogicalBinaryExpr logical | e = logical.getRightOperand() and inVoidContext(logical))
3942
}
43+
44+
45+
/**
46+
* Holds if `e` is of the form `x;` or `e.p;` and has a JSDoc comment containing a tag.
47+
* In that case, it is probably meant as a declaration and shouldn't be flagged by this query.
48+
*
49+
* This will still flag cases where the JSDoc comment contains no tag at all (and hence carries
50+
* no semantic information), and expression statements with an ordinary (non-JSDoc) comment
51+
* attached to them.
52+
*/
53+
predicate isDeclaration(Expr e) {
54+
(e instanceof VarAccess or e instanceof PropAccess) and
55+
exists(e.getParent().(ExprStmt).getDocumentation().getATag())
56+
}
57+
58+
/**
59+
* Holds if there exists a getter for a property called `name` anywhere in the program.
60+
*/
61+
predicate isGetterProperty(string name) {
62+
// there is a call of the form `Object.defineProperty(..., name, descriptor)` ...
63+
exists(CallToObjectDefineProperty defProp | name = defProp.getPropertyName() |
64+
// ... where `descriptor` defines a getter
65+
defProp.hasPropertyAttributeWrite("get", _)
66+
or
67+
// ... where `descriptor` may define a getter
68+
exists(DataFlow::SourceNode descriptor | descriptor.flowsTo(defProp.getPropertyDescriptor()) |
69+
descriptor.isIncomplete(_)
70+
or
71+
// minimal escape analysis for the descriptor
72+
exists(DataFlow::InvokeNode invk |
73+
not invk = defProp and
74+
descriptor.flowsTo(invk.getAnArgument())
75+
)
76+
)
77+
)
78+
or
79+
// there is an object expression with a getter property `name`
80+
exists(ObjectExpr obj | obj.getPropertyByName(name) instanceof PropertyGetter)
81+
}
82+
83+
/**
84+
* A property access that may invoke a getter.
85+
*/
86+
class GetterPropertyAccess extends PropAccess {
87+
override predicate isImpure() { isGetterProperty(getPropertyName()) }
88+
}
89+
90+
/**
91+
* Holds if `c` is an indirect eval call of the form `(dummy, eval)(...)`, where
92+
* `dummy` is some expression whose value is discarded, and which simply
93+
* exists to prevent the call from being interpreted as a direct eval.
94+
*/
95+
predicate isIndirectEval(CallExpr c, Expr dummy) {
96+
exists(SeqExpr seq | seq = c.getCallee().stripParens() |
97+
dummy = seq.getOperand(0) and
98+
seq.getOperand(1).(GlobalVarAccess).getName() = "eval" and
99+
seq.getNumOperands() = 2
100+
)
101+
}
102+
103+
/**
104+
* Holds if `c` is a call of the form `(dummy, e[p])(...)`, where `dummy` is
105+
* some expression whose value is discarded, and which simply exists
106+
* to prevent the call from being interpreted as a method call.
107+
*/
108+
predicate isReceiverSuppressingCall(CallExpr c, Expr dummy, PropAccess callee) {
109+
exists(SeqExpr seq | seq = c.getCallee().stripParens() |
110+
dummy = seq.getOperand(0) and
111+
seq.getOperand(1) = callee and
112+
seq.getNumOperands() = 2
113+
)
114+
}
115+
116+
/**
117+
* Holds if evaluating `e` has no side effects (except potentially allocating
118+
* and initializing a new object).
119+
*
120+
* For calls, we do not check whether their arguments have any side effects:
121+
* even if they do, the call itself is useless and should be flagged by this
122+
* query.
123+
*/
124+
predicate noSideEffects(Expr e) {
125+
e.isPure()
126+
or
127+
// `new Error(...)`, `new SyntaxError(...)`, etc.
128+
forex(Function f | f = e.flow().(DataFlow::NewNode).getACallee() |
129+
f.(ExternalType).getASupertype*().getName() = "Error"
130+
)
131+
}
132+
133+
/**
134+
* Holds if the expression `e` should be reported as having no effect.
135+
*/
136+
predicate hasNoEffect(Expr e) {
137+
noSideEffects(e) and
138+
inVoidContext(e) and
139+
// disregard pure expressions wrapped in a void(...)
140+
not e instanceof VoidExpr and
141+
// filter out directives (unknown directives are handled by UnknownDirective.ql)
142+
not exists(Directive d | e = d.getExpr()) and
143+
// or about externs
144+
not e.inExternsFile() and
145+
// don't complain about declarations
146+
not isDeclaration(e) and
147+
// exclude DOM properties, which sometimes have magical auto-update properties
148+
not isDOMProperty(e.(PropAccess).getPropertyName()) and
149+
// exclude xUnit.js annotations
150+
not e instanceof XUnitAnnotation and
151+
// exclude common patterns that are most likely intentional
152+
not isIndirectEval(_, e) and
153+
not isReceiverSuppressingCall(_, e, _) and
154+
// exclude anonymous function expressions as statements; these can only arise
155+
// from a syntax error we already flag
156+
not exists(FunctionExpr fe, ExprStmt es | fe = e |
157+
fe = es.getExpr() and
158+
not exists(fe.getName())
159+
)
160+
}

javascript/ql/src/Statements/UseOfReturnlessFunction.ql

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,17 +82,66 @@ predicate alwaysThrows(Function f) {
8282
)
8383
}
8484

85-
from DataFlow::CallNode call
86-
where
85+
predicate callToVoidFunction(DataFlow::CallNode call, Function func) {
8786
not call.isIndefinite(_) and
88-
forex(Function f | f = call.getACallee() |
87+
func = call.getACallee() and
88+
forall(Function f | f = call.getACallee() |
8989
returnsVoid(f) and not isStub(f) and not alwaysThrows(f)
90+
)
91+
}
92+
93+
predicate hasNonVoidCallbackMethod(string name) {
94+
name = "every" or
95+
name = "filter" or
96+
name = "find" or
97+
name = "findIndex" or
98+
name = "flatMap" or
99+
name = "map" or
100+
name = "reduce" or
101+
name = "reduceRight" or
102+
name = "some" or
103+
name = "sort"
104+
}
105+
106+
DataFlow::SourceNode array(DataFlow::TypeTracker t) {
107+
t.start() and result instanceof DataFlow::ArrayCreationNode
108+
or
109+
exists (DataFlow::TypeTracker t2 |
110+
result = array(t2).track(t2, t)
111+
)
112+
}
113+
114+
DataFlow::SourceNode array() { result = array(DataFlow::TypeTracker::end()) }
115+
116+
predicate voidArrayCallback(DataFlow::MethodCallNode call, Function func) {
117+
hasNonVoidCallbackMethod(call.getMethodName()) and
118+
func = call.getAnArgument().getALocalSource().asExpr() and
119+
1 = count(DataFlow::Node arg | arg = call.getAnArgument() and arg.getALocalSource().asExpr() instanceof Function) and
120+
returnsVoid(func) and
121+
not isStub(func) and
122+
not alwaysThrows(func) and
123+
(
124+
call.getReceiver().getALocalSource() = array()
125+
or
126+
call.getCalleeNode() instanceof LodashUnderscore::Member
127+
)
128+
}
129+
130+
from DataFlow::CallNode call, Function func, string name, string msg
131+
where
132+
(
133+
callToVoidFunction(call, func) and
134+
msg = "the $@ does not return anything, yet the return value is used." and
135+
name = "function " + call.getCalleeName()
136+
or
137+
voidArrayCallback(call, func) and
138+
msg = "the $@ does not return anything, yet the return value from the call to " + call.getCalleeName() + "() is used." and
139+
name = "callback function"
90140
) and
91-
92141
not benignContext(call.asExpr()) and
93-
142+
// Avoid double reporting from js/useless-expression
143+
not hasNoEffect(func.getBodyStmt(func.getNumBodyStmt() - 1).(ExprStmt).getExpr()) and
94144
// anonymous one-shot closure. Those are used in weird ways and we ignore them.
95145
not oneshotClosure(call.asExpr())
96146
select
97-
call, "the function $@ does not return anything, yet the return value is used.", call.getACallee(), call.getCalleeName()
98-
147+
call, msg, func, name

0 commit comments

Comments
 (0)