-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Expand file tree
/
Copy pathDeadStoreOfProperty.ql
More file actions
142 lines (134 loc) · 5.15 KB
/
DeadStoreOfProperty.ql
File metadata and controls
142 lines (134 loc) · 5.15 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
138
139
140
141
142
/**
* @name Useless assignment to property
* @description An assignment to a property whose value is always overwritten has no effect.
* @kind problem
* @problem.severity warning
* @id js/useless-assignment-to-property
* @tags maintainability
* @precision high
*/
import javascript
import Expressions.DOMProperties
import DeadStore
/**
* Holds if `write` writes to property `name` of `base`, and `base` is the only base object of `write`.
*/
predicate unambiguousPropWrite(DataFlow::SourceNode base, string name, DataFlow::PropWrite write) {
write = base.getAPropertyWrite(name) and
not exists (DataFlow::SourceNode otherBase |
otherBase != base and
write = otherBase.getAPropertyWrite(name)
)
}
/**
* Holds if `assign1` and `assign2` both assign property `name` of the same object, and `assign2` post-dominates `assign1`.
*/
predicate postDominatedPropWrite(string name, DataFlow::PropWrite assign1, DataFlow::PropWrite assign2) {
exists (ControlFlowNode write1, ControlFlowNode write2, DataFlow::SourceNode base, ReachableBasicBlock block1, ReachableBasicBlock block2 |
write1 = assign1.getWriteNode() and
write2 = assign2.getWriteNode() and
block1 = write1.getBasicBlock() and
block2 = write2.getBasicBlock() and
unambiguousPropWrite(base, name, assign1) and
unambiguousPropWrite(base, name, assign2) and
block2.postDominates(block1) and
(block1 = block2 implies
exists (int i1, int i2 |
write1 = block1.getNode(i1) and
write2 = block2.getNode(i2) and
i1 < i2
)
)
)
}
/**
* Holds if `e` may access a property named `name`.
*/
bindingset[name]
predicate maybeAccessesProperty(Expr e, string name) {
(e.(PropAccess).getPropertyName() = name and e instanceof RValue) or
// conservatively reject all side-effects
e.isImpure()
}
/**
* Holds if `assign1` and `assign2` both assign property `name`, but `assign1` is dead because of `assign2`.
*/
predicate isDeadAssignment(string name, DataFlow::PropWrite assign1, DataFlow::PropWrite assign2) {
postDominatedPropWrite(name, assign1, assign2) and
noPropAccessBetween(name, assign1, assign2) and
not isDOMProperty(name)
}
/**
* Holds if `assign` assigns a property `name` that may be accessed somewhere else in the same block,
* `after` indicates if the access happens before or after the node for `assign`.
*/
bindingset[name]
predicate maybeAccessesAssignedPropInBlock(string name, DataFlow::PropWrite assign, boolean after) {
exists (ControlFlowNode write, ReachableBasicBlock block, int i, int j, Expr e |
write = assign.getWriteNode() and
block = assign.getBasicBlock() and
write = block.getNode(i) and
e = block.getNode(j) and
maybeAccessesProperty(e, name) |
after = true and i < j
or
after = false and j < i
)
}
/**
* Holds if `assign1` and `assign2` both assign property `name`, and the assigned property is not accessed between the two assignments.
*/
bindingset[name]
predicate noPropAccessBetween(string name, DataFlow::PropWrite assign1, DataFlow::PropWrite assign2) {
exists (ControlFlowNode write1, ControlFlowNode write2, ReachableBasicBlock block1, ReachableBasicBlock block2 |
write1 = assign1.getWriteNode() and
write2 = assign2.getWriteNode() and
write1.getBasicBlock() = block1 and
write2.getBasicBlock() = block2 and
if block1 = block2 then
// same block: check for access between
not exists (int i1, Expr mid, int i2 |
assign1.getWriteNode() = block1.getNode(i1) and
assign2.getWriteNode() = block2.getNode(i2) and
mid = block1.getNode([i1+1..i2-1]) and
maybeAccessesProperty(mid, name)
)
else
// other block:
not (
// check for an access after the first write node
maybeAccessesAssignedPropInBlock(name, assign1, true) or
// check for an access between the two write blocks
exists (ReachableBasicBlock mid |
block1.getASuccessor+() = mid and
mid.getASuccessor+() = block2 |
maybeAccessesProperty(mid.getANode(), name)
) or
// check for an access before the second write node
maybeAccessesAssignedPropInBlock(name, assign2, false)
)
)
}
from string name, DataFlow::PropWrite assign1, DataFlow::PropWrite assign2
where isDeadAssignment(name, assign1, assign2) and
// whitelist
not (
// Google Closure Compiler pattern: `o.p = o['p'] = v`
exists (PropAccess p1, PropAccess p2 |
p1 = assign1.getAstNode() and
p2 = assign2.getAstNode() |
p1 instanceof DotExpr and p2 instanceof IndexExpr
or
p2 instanceof DotExpr and p1 instanceof IndexExpr
)
or
// don't flag overwrites for default values
isDefaultInit(assign1.getRhs().asExpr().getUnderlyingValue())
or
// don't flag assignments in externs
assign1.getAstNode().inExternsFile()
or
// exclude result from js/overwritten-property
assign2.getBase() instanceof DataFlow::ObjectLiteralNode
)
select assign1.getWriteNode(), "This write to property '" + name + "' is useless, since $@ always overrides it.", assign2.getWriteNode(), "another property write"