mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
JS: add query js/unused-property
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
+ semmlecode-javascript-queries/Declarations/DeadStoreOfProperty.ql: /Maintainability/Declarations
|
||||
+ semmlecode-javascript-queries/Declarations/DuplicateVarDecl.ql: /Maintainability/Declarations
|
||||
+ semmlecode-javascript-queries/Declarations/UnusedParameter.ql: /Maintainability/Declarations
|
||||
+ semmlecode-javascript-queries/Declarations/UnusedProperty.ql: /Maintainability/Declarations
|
||||
+ semmlecode-javascript-queries/Declarations/UnusedVariable.ql: /Maintainability/Declarations
|
||||
+ semmlecode-javascript-queries/Expressions/UnneededDefensiveProgramming.ql: /Maintainability/Expressions
|
||||
+ semmlecode-javascript-queries/LanguageFeatures/ArgumentsCallerCallee.ql: /Maintainability/Language Features
|
||||
|
||||
34
javascript/ql/src/Declarations/UnusedProperty.qhelp
Normal file
34
javascript/ql/src/Declarations/UnusedProperty.qhelp
Normal file
@@ -0,0 +1,34 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
Unused object properties make code harder to maintain and use. Clients that are unaware that a
|
||||
property is unused may perform nontrivial computations to compute a value that is ultimately
|
||||
unused.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>Remove the unused property.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>
|
||||
In this code, the function <code>f</code> initializes a property <code>prop_a</code> with a
|
||||
call to the function <code>expensiveComputation</code>, but later on this property is never read.
|
||||
Removing <code>prop</code> would improve code quality and performance.
|
||||
</p>
|
||||
|
||||
<sample src="examples/UnusedProperty.js" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>Coding Horror: <a href="http://blog.codinghorror.com/code-smells/">Code Smells</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
74
javascript/ql/src/Declarations/UnusedProperty.ql
Normal file
74
javascript/ql/src/Declarations/UnusedProperty.ql
Normal file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* @name Unused property
|
||||
* @description Unused properties may be a symptom of a bug and should be examined carefully.
|
||||
* @kind problem
|
||||
* @problem.severity recommendation
|
||||
* @id js/unused-property
|
||||
* @tags maintainability
|
||||
* @precision high
|
||||
*/
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.dataflow.CapturedNodes
|
||||
import UnusedVariable
|
||||
import UnusedParameter
|
||||
import Expressions.ExprHasNoEffect
|
||||
|
||||
predicate hasUnknownPropertyRead(CapturedSource obj) {
|
||||
// dynamic reads
|
||||
exists(DataFlow::PropRead r | obj.getAPropertyRead() = r | not exists(r.getPropertyName()))
|
||||
or
|
||||
// reflective reads
|
||||
obj.flowsToExpr(any(EnhancedForLoop l).getIterationDomain())
|
||||
or
|
||||
obj.flowsToExpr(any(InExpr l).getRightOperand())
|
||||
or
|
||||
obj.flowsToExpr(any(SpreadElement e).getOperand())
|
||||
or
|
||||
exists(obj.getAPropertyRead("hasOwnProperty"))
|
||||
or
|
||||
exists(obj.getAPropertyRead("propertyIsEnumerable"))
|
||||
}
|
||||
|
||||
predicate flowsToTypeRestrictedExpression(CapturedSource n) {
|
||||
exists (Expr restricted, TypeExpr type |
|
||||
n.flowsToExpr(restricted) and
|
||||
not type.isAny() |
|
||||
exists (TypeAssertion assertion |
|
||||
type = assertion.getTypeAnnotation() and
|
||||
restricted = assertion.getExpression()
|
||||
)
|
||||
or
|
||||
exists (BindingPattern v |
|
||||
type = v.getTypeAnnotation() and
|
||||
restricted = v.getAVariable().getAnAssignedExpr()
|
||||
)
|
||||
// no need to reason about writes to typed fields, captured nodes do not reach them
|
||||
)
|
||||
}
|
||||
|
||||
from DataFlow::PropWrite w, CapturedSource n, string name
|
||||
where
|
||||
w = n.getAPropertyWrite(name) and
|
||||
not exists(n.getAPropertyRead(name)) and
|
||||
not w.getBase().analyze().getAValue() != n.analyze().getAValue() and
|
||||
not hasUnknownPropertyRead(n) and
|
||||
// avoid reporting if the definition is unreachable
|
||||
w.getAstNode().getFirstControlFlowNode().getBasicBlock() instanceof ReachableBasicBlock and
|
||||
// avoid implicitly read properties
|
||||
not (
|
||||
name = "toString" or
|
||||
name = "valueOf" or
|
||||
name.matches("@@%") // @@iterator, for example
|
||||
) and
|
||||
// avoid flagging properties that a type system requires
|
||||
not flowsToTypeRestrictedExpression(n) and
|
||||
// flagged by js/unused-local-variable
|
||||
not exists(UnusedLocal l | l.getAnAssignedExpr().getUnderlyingValue().flow() = n) and
|
||||
// flagged by js/unused-parameter
|
||||
not exists(Parameter p | isAnAccidentallyUnusedParameter(p) |
|
||||
p.getDefault().getUnderlyingValue().flow() = n
|
||||
) and
|
||||
// flagged by js/useless-expression
|
||||
not inVoidContext(n.asExpr())
|
||||
select w, "Unused property " + name + "."
|
||||
@@ -10,23 +10,7 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* A local variable that is neither used nor exported, and is not a parameter
|
||||
* or a function name.
|
||||
*/
|
||||
class UnusedLocal extends LocalVariable {
|
||||
UnusedLocal() {
|
||||
not exists(getAnAccess()) and
|
||||
not exists(Parameter p | this = p.getAVariable()) and
|
||||
not exists(FunctionExpr fe | this = fe.getVariable()) and
|
||||
not exists(ClassExpr ce | this = ce.getVariable()) and
|
||||
not exists(ExportDeclaration ed | ed.exportsAs(this, _)) and
|
||||
not exists(LocalVarTypeAccess type | type.getVariable() = this) and
|
||||
// common convention: variables with leading underscore are intentionally unused
|
||||
getName().charAt(0) != "_"
|
||||
}
|
||||
}
|
||||
import UnusedVariable
|
||||
|
||||
/**
|
||||
* Holds if `v` is mentioned in a JSDoc comment in the same file, and that file
|
||||
|
||||
22
javascript/ql/src/Declarations/UnusedVariable.qll
Normal file
22
javascript/ql/src/Declarations/UnusedVariable.qll
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* This library contains parts of the 'js/unused-local-variable' query implementation.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* A local variable that is neither used nor exported, and is not a parameter
|
||||
* or a function name.
|
||||
*/
|
||||
class UnusedLocal extends LocalVariable {
|
||||
UnusedLocal() {
|
||||
not exists(getAnAccess()) and
|
||||
not exists(Parameter p | this = p.getAVariable()) and
|
||||
not exists(FunctionExpr fe | this = fe.getVariable()) and
|
||||
not exists(ClassExpr ce | this = ce.getVariable()) and
|
||||
not exists(ExportDeclaration ed | ed.exportsAs(this, _)) and
|
||||
not exists(LocalVarTypeAccess type | type.getVariable() = this) and
|
||||
// common convention: variables with leading underscore are intentionally unused
|
||||
getName().charAt(0) != "_"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
function f() {
|
||||
var o = {
|
||||
prop_a: expensiveComputation(),
|
||||
prop_b: anotherComputation()
|
||||
};
|
||||
|
||||
return o.prop_b;
|
||||
}
|
||||
@@ -16,40 +16,7 @@ import javascript
|
||||
import DOMProperties
|
||||
import semmle.javascript.frameworks.xUnit
|
||||
import semmle.javascript.RestrictedLocations
|
||||
|
||||
/**
|
||||
* Holds if `e` appears in a syntactic context where its value is discarded.
|
||||
*/
|
||||
predicate inVoidContext(Expr e) {
|
||||
exists(ExprStmt parent |
|
||||
// e is a toplevel expression in an expression statement
|
||||
parent = e.getParent() and
|
||||
// but it isn't an HTML attribute or a configuration object
|
||||
not exists(TopLevel tl | tl = parent.getParent() |
|
||||
tl instanceof CodeInAttribute
|
||||
or
|
||||
// if the toplevel in its entirety is of the form `({ ... })`,
|
||||
// it is probably a configuration object (e.g., a require.js build configuration)
|
||||
tl.getNumChildStmt() = 1 and e.stripParens() instanceof ObjectExpr
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(SeqExpr seq, int i, int n |
|
||||
e = seq.getOperand(i) and
|
||||
n = seq.getNumOperands()
|
||||
|
|
||||
i < n - 1 or inVoidContext(seq)
|
||||
)
|
||||
or
|
||||
exists(ForStmt stmt | e = stmt.getUpdate())
|
||||
or
|
||||
exists(ForStmt stmt | e = stmt.getInit() |
|
||||
// Allow the pattern `for(i; i < 10; i++)`
|
||||
not e instanceof VarAccess
|
||||
)
|
||||
or
|
||||
exists(LogicalBinaryExpr logical | e = logical.getRightOperand() and inVoidContext(logical))
|
||||
}
|
||||
import ExprHasNoEffect
|
||||
|
||||
/**
|
||||
* Holds if `e` is of the form `x;` or `e.p;` and has a JSDoc comment containing a tag.
|
||||
|
||||
39
javascript/ql/src/Expressions/ExprHasNoEffect.qll
Normal file
39
javascript/ql/src/Expressions/ExprHasNoEffect.qll
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* This library contains parts of the 'js/useless-expression' query implementation.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* Holds if `e` appears in a syntactic context where its value is discarded.
|
||||
*/
|
||||
predicate inVoidContext(Expr e) {
|
||||
exists(ExprStmt parent |
|
||||
// e is a toplevel expression in an expression statement
|
||||
parent = e.getParent() and
|
||||
// but it isn't an HTML attribute or a configuration object
|
||||
not exists(TopLevel tl | tl = parent.getParent() |
|
||||
tl instanceof CodeInAttribute
|
||||
or
|
||||
// if the toplevel in its entirety is of the form `({ ... })`,
|
||||
// it is probably a configuration object (e.g., a require.js build configuration)
|
||||
tl.getNumChildStmt() = 1 and e.stripParens() instanceof ObjectExpr
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(SeqExpr seq, int i, int n |
|
||||
e = seq.getOperand(i) and
|
||||
n = seq.getNumOperands()
|
||||
|
|
||||
i < n - 1 or inVoidContext(seq)
|
||||
)
|
||||
or
|
||||
exists(ForStmt stmt | e = stmt.getUpdate())
|
||||
or
|
||||
exists(ForStmt stmt | e = stmt.getInit() |
|
||||
// Allow the pattern `for(i; i < 10; i++)`
|
||||
not e instanceof VarAccess
|
||||
)
|
||||
or
|
||||
exists(LogicalBinaryExpr logical | e = logical.getRightOperand() and inVoidContext(logical))
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
| tst.js:4:9:4:19 | unused1: 42 | Unused property unused1. |
|
||||
| tst.js:19:5:19:15 | unused9: 42 | Unused property unused9. |
|
||||
| tst.js:26:13:26:24 | unused11: 42 | Unused property unused11. |
|
||||
| tst.js:31:13:31:35 | used12_ ... lly: 42 | Unused property used12_butNotReally. |
|
||||
| tst.js:32:13:32:24 | unused12: 42 | Unused property unused12. |
|
||||
| tst.js:52:3:52:14 | unused14: 42 | Unused property unused14. |
|
||||
| tst.js:54:2:54:20 | captured14.unused14 | Unused property unused14. |
|
||||
| tst.js:55:2:55:20 | captured14.unused14 | Unused property unused14. |
|
||||
| tst.ts:24:21:24:25 | p: 42 | Unused property p. |
|
||||
@@ -0,0 +1 @@
|
||||
Declarations/UnusedProperty.ql
|
||||
@@ -0,0 +1,83 @@
|
||||
(function(){
|
||||
var captured1 = {
|
||||
used1: 42,
|
||||
unused1: 42
|
||||
};
|
||||
captured1.used1;
|
||||
|
||||
var unused2 = {
|
||||
unused2a: 42,
|
||||
unused2b: 42
|
||||
};
|
||||
|
||||
for (x.p in { used3: 42 });
|
||||
for (x.p of { used4: 42 });
|
||||
42 in { used5: 42 };
|
||||
f(...{used6: 42});
|
||||
[...{used7: 42}];
|
||||
({...{used8: 42}});
|
||||
({ unused9: 42 }) + "";
|
||||
({ used10: 42 }).hasOwnProperty;
|
||||
({ used10: 42 }).propertyIsEnumerable;
|
||||
|
||||
(function(){
|
||||
var captured11 = {
|
||||
used11: 42,
|
||||
unused11: 42
|
||||
};
|
||||
captured11.used11;
|
||||
|
||||
var captured12 = {
|
||||
used12_butNotReally: 42,
|
||||
unused12: 42
|
||||
};
|
||||
|
||||
throw x;
|
||||
|
||||
captured12.used12_butNotReally;
|
||||
|
||||
var captured13 = {
|
||||
used13: 42,
|
||||
unused13: 42
|
||||
};
|
||||
captured13.used13;
|
||||
});
|
||||
(function(options){
|
||||
if(unknown)
|
||||
options = {};
|
||||
options.output = 42;
|
||||
});
|
||||
|
||||
var captured14 = {
|
||||
unused14: 42
|
||||
};
|
||||
captured14.unused14 = 42;
|
||||
captured14.unused14 = 42;
|
||||
|
||||
|
||||
var captured15 = {
|
||||
semiUnused15: 42
|
||||
};
|
||||
captured15.semiUnused15 = 42;
|
||||
captured15.semiUnused15;
|
||||
});
|
||||
(function(unusedParam = {unusedProp: 42}){
|
||||
|
||||
});
|
||||
(function(){
|
||||
var unusedObj = {
|
||||
unusedProp: 42
|
||||
};
|
||||
});
|
||||
(function(){
|
||||
var unusedSpecials = {
|
||||
toString: function(){},
|
||||
valueOf: function(){},
|
||||
'@@iterator': function(){}
|
||||
};
|
||||
unusedSpecials.foo;
|
||||
});
|
||||
|
||||
(function(){
|
||||
({ unusedProp: 42 }, 42);
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
(function(){
|
||||
var o1: { p: int, q: int } = { p: 42, q: 42 };
|
||||
o1.q;
|
||||
|
||||
var o2 = <{ p: int, q: int }>{ p: 42, q: 42 };
|
||||
o2.q;
|
||||
|
||||
var o3: { p: int, q: int } = f();
|
||||
o3 = o3 || { p: 42, q: 42 };
|
||||
o3.q;
|
||||
|
||||
});
|
||||
|
||||
class C {
|
||||
private o: { p: int, q: int };
|
||||
|
||||
constructor() {
|
||||
this.o = { p: 42, q: 42 };
|
||||
this.o.q;
|
||||
}
|
||||
}
|
||||
|
||||
(function(){
|
||||
var o1: any = { p: 42, q: 42 };
|
||||
o1.q;
|
||||
var o2: any = { p: 42, q: 42 };
|
||||
var o3: { p: int, q: int } = o2;
|
||||
})
|
||||
Reference in New Issue
Block a user