Skip to content

Commit b20232d

Browse files
committed
Python: Simplify guards as suggested
1 parent 43effd2 commit b20232d

File tree

4 files changed

+61
-73
lines changed

4 files changed

+61
-73
lines changed

python/ql/src/semmle/python/functions/ModificationOfParameterWithDefault.qll

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,13 @@ module ModificationOfParameterWithDefault {
3636
override predicate isBarrier(DataFlow::Node node) { node instanceof Barrier }
3737

3838
override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
39-
// if we are tracking a empty default, then we should not modify falsey values
40-
nonEmptyDefault = false and guard instanceof BlocksFalsey
39+
// if we are tracking a empty default, then it is ok to modify truthy values,
40+
// so our tracking ends at those.
41+
nonEmptyDefault = false and guard instanceof BlocksTruthy
4142
or
42-
// if we are tracking a non-empty default, then we should not modify truthy values
43-
nonEmptyDefault = true and guard instanceof BlocksTruthy
43+
// if we are tracking a non-empty default, then it is ok to modify falsy values,
44+
// so our tracking ends at those.
45+
nonEmptyDefault = true and guard instanceof BlocksFalsey
4446
}
4547
}
4648
}

python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll

Lines changed: 25 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,26 @@ module ModificationOfParameterWithDefault {
3434
/**
3535
* A sanitizer guard that does not let a truthy value flow to the true branch.
3636
*
37+
* Implementation note:
3738
* Since guards with different behaviour cannot exist on the same node,
3839
* we let all guards have the same behaviour, in the sense that they all check
3940
* the true branch. Instead, we partition guards into those that block
4041
* truthy values and those that block falsy values.
42+
*
43+
* If you extend this class, make sure that your barrier checks the true branch.
4144
*/
4245
abstract class BlocksTruthy extends DataFlow::BarrierGuard { }
4346

4447
/**
4548
* A sanitizer guard that does not let a falsy value flow to the true branch.
4649
*
50+
* Implementation note:
4751
* Since guards with different behaviour cannot exist on the same node,
4852
* we let all guards have the same behaviour, in the sense that they all check
4953
* the true branch. Instead, we partition guards into those that block
5054
* truthy values and those that block falsy values.
55+
*
56+
* If you extend this class, make sure that your barrier checks the true branch.
5157
*/
5258
abstract class BlocksFalsey extends DataFlow::BarrierGuard { }
5359

@@ -135,78 +141,40 @@ module ModificationOfParameterWithDefault {
135141
}
136142

137143
/**
138-
* An expression that is checked directly in an `if`, possibly with `not`, such as `if x:` or `if not x:`.
139-
*/
140-
private class IdentityGuarded extends Expr {
141-
boolean inverted;
142-
143-
IdentityGuarded() {
144-
this = any(If i).getTest() and
145-
inverted = false
146-
or
147-
exists(IdentityGuarded ig, UnaryExpr notExp |
148-
notExp.getOp() instanceof Not and
149-
ig = notExp and
150-
notExp.getOperand() = this
151-
|
152-
inverted = ig.isInverted().booleanNot()
153-
)
154-
}
155-
156-
/**
157-
* Whether this guard has been inverted. For `if x:` the result is `false`, and for `if not x:` the result is `true`.
158-
*/
159-
boolean isInverted() { result = inverted }
160-
}
161-
162-
/**
163-
* Holds iff `guard` is checking the `Name` represented by `guarded` for truthyness.
164-
* `result` is true if the check is inverted and false if it is not.
165-
*/
166-
boolean isIdentityGuard(DataFlow::GuardNode guard, ControlFlowNode guarded) {
167-
exists(IdentityGuarded ig |
168-
ig instanceof Name and
169-
// In `not l`, the `ControlFlowNode` for `l` is not an instance of `GuardNode`.
170-
// TODO: This is slightly naive, not handling e.g. `l or cond` correctly.
171-
// We should change it when we have a proper guards library.
172-
guard.getNode().getAChildNode*() = ig and
173-
result = ig.isInverted() and
174-
guarded.getNode() = ig
175-
)
176-
}
177-
178-
/**
179-
* A sanitizer guard that does not let a truthy value flow to the true branch.
180-
* Based on `isIdentityGuard`, so comes with the same caveats.
144+
* A simple sanitizer guard that does not let a truthy value flow to the true branch.
145+
*
146+
* Blocks flow of `x` in the true branch in the example below.
147+
* ```py
148+
* if x:
149+
* x.append(42)
150+
* ```
181151
*/
182152
class BlocksTruthyGuard extends BlocksTruthy {
183153
ControlFlowNode guarded;
184154

185-
BlocksTruthyGuard() {
186-
// The raw guard is true if the value is non-empty.
187-
// We wish to send truthy falues to the false branch,
188-
// se we are looking for inverted guards.
189-
isIdentityGuard(this, guarded) = true
190-
}
155+
BlocksTruthyGuard() { this instanceof NameNode }
191156

192157
override predicate checks(ControlFlowNode node, boolean branch) {
193-
node = guarded and
158+
node = this and
194159
branch = true
195160
}
196161
}
197162

198163
/**
199-
* A sanitizer guard that does not let a falsy value flow to the true branch.
200-
* Based on `isIdentityGuard`, so comes with the same caveats.
164+
* A simple sanitizer guard that does not let a truthy value flow to the true branch.
165+
*
166+
* Blocks flow of `x` in the true branch in the example below.
167+
* ```py
168+
* if not x:
169+
* x.append(42)
170+
* ```
201171
*/
202172
class BlocksFalseyGuard extends BlocksFalsey {
203-
ControlFlowNode guarded;
173+
NameNode guarded;
204174

205175
BlocksFalseyGuard() {
206-
// The raw guard is true if the value is non-empty.
207-
// We wish to send falsy falues to the false branch,
208-
// se we are looking for guards that are not inverted.
209-
isIdentityGuard(this, guarded) = false
176+
this.(UnaryExprNode).getNode().getOp() instanceof Not and
177+
guarded = this.(UnaryExprNode).getOperand()
210178
}
211179

212180
override predicate checks(ControlFlowNode node, boolean branch) {

python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/ModificationOfParameterWithDefault.expected

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,12 @@ edges
2424
| test.py:108:14:108:14 | ControlFlowNode for d | test.py:109:9:109:9 | ControlFlowNode for d |
2525
| test.py:113:20:113:20 | ControlFlowNode for d | test.py:115:5:115:5 | ControlFlowNode for d |
2626
| test.py:119:29:119:29 | ControlFlowNode for d | test.py:121:5:121:5 | ControlFlowNode for d |
27-
| test.py:137:15:137:15 | ControlFlowNode for l | test.py:139:9:139:9 | ControlFlowNode for l |
28-
| test.py:143:23:143:23 | ControlFlowNode for l | test.py:145:9:145:9 | ControlFlowNode for l |
27+
| test.py:124:15:124:15 | ControlFlowNode for l | test.py:128:9:128:9 | ControlFlowNode for l |
28+
| test.py:131:23:131:23 | ControlFlowNode for l | test.py:135:9:135:9 | ControlFlowNode for l |
29+
| test.py:138:15:138:15 | ControlFlowNode for l | test.py:140:9:140:9 | ControlFlowNode for l |
30+
| test.py:138:15:138:15 | ControlFlowNode for l | test.py:142:9:142:9 | ControlFlowNode for l |
31+
| test.py:145:23:145:23 | ControlFlowNode for l | test.py:147:9:147:9 | ControlFlowNode for l |
32+
| test.py:145:23:145:23 | ControlFlowNode for l | test.py:149:9:149:9 | ControlFlowNode for l |
2933
nodes
3034
| test.py:2:12:2:12 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
3135
| test.py:3:5:3:5 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
@@ -71,10 +75,16 @@ nodes
7175
| test.py:115:5:115:5 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
7276
| test.py:119:29:119:29 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
7377
| test.py:121:5:121:5 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
74-
| test.py:137:15:137:15 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
75-
| test.py:139:9:139:9 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
76-
| test.py:143:23:143:23 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
77-
| test.py:145:9:145:9 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
78+
| test.py:124:15:124:15 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
79+
| test.py:128:9:128:9 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
80+
| test.py:131:23:131:23 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
81+
| test.py:135:9:135:9 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
82+
| test.py:138:15:138:15 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
83+
| test.py:140:9:140:9 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
84+
| test.py:142:9:142:9 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
85+
| test.py:145:23:145:23 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
86+
| test.py:147:9:147:9 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
87+
| test.py:149:9:149:9 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
7888
#select
7989
| test.py:3:5:3:5 | ControlFlowNode for l | test.py:2:12:2:12 | ControlFlowNode for l | test.py:3:5:3:5 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:2:12:2:12 | ControlFlowNode for l | Default value |
8090
| test.py:8:5:8:5 | ControlFlowNode for l | test.py:7:11:7:11 | ControlFlowNode for l | test.py:8:5:8:5 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:7:11:7:11 | ControlFlowNode for l | Default value |
@@ -95,5 +105,9 @@ nodes
95105
| test.py:109:9:109:9 | ControlFlowNode for d | test.py:108:14:108:14 | ControlFlowNode for d | test.py:109:9:109:9 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:108:14:108:14 | ControlFlowNode for d | Default value |
96106
| test.py:115:5:115:5 | ControlFlowNode for d | test.py:113:20:113:20 | ControlFlowNode for d | test.py:115:5:115:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:113:20:113:20 | ControlFlowNode for d | Default value |
97107
| test.py:121:5:121:5 | ControlFlowNode for d | test.py:119:29:119:29 | ControlFlowNode for d | test.py:121:5:121:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:119:29:119:29 | ControlFlowNode for d | Default value |
98-
| test.py:139:9:139:9 | ControlFlowNode for l | test.py:137:15:137:15 | ControlFlowNode for l | test.py:139:9:139:9 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:137:15:137:15 | ControlFlowNode for l | Default value |
99-
| test.py:145:9:145:9 | ControlFlowNode for l | test.py:143:23:143:23 | ControlFlowNode for l | test.py:145:9:145:9 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:143:23:143:23 | ControlFlowNode for l | Default value |
108+
| test.py:128:9:128:9 | ControlFlowNode for l | test.py:124:15:124:15 | ControlFlowNode for l | test.py:128:9:128:9 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:124:15:124:15 | ControlFlowNode for l | Default value |
109+
| test.py:135:9:135:9 | ControlFlowNode for l | test.py:131:23:131:23 | ControlFlowNode for l | test.py:135:9:135:9 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:131:23:131:23 | ControlFlowNode for l | Default value |
110+
| test.py:140:9:140:9 | ControlFlowNode for l | test.py:138:15:138:15 | ControlFlowNode for l | test.py:140:9:140:9 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:138:15:138:15 | ControlFlowNode for l | Default value |
111+
| test.py:142:9:142:9 | ControlFlowNode for l | test.py:138:15:138:15 | ControlFlowNode for l | test.py:142:9:142:9 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:138:15:138:15 | ControlFlowNode for l | Default value |
112+
| test.py:147:9:147:9 | ControlFlowNode for l | test.py:145:23:145:23 | ControlFlowNode for l | test.py:147:9:147:9 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:145:23:145:23 | ControlFlowNode for l | Default value |
113+
| test.py:149:9:149:9 | ControlFlowNode for l | test.py:145:23:145:23 | ControlFlowNode for l | test.py:149:9:149:9 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:145:23:145:23 | ControlFlowNode for l | Default value |

python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,26 +121,30 @@ def dict_update_op_nochange(d = {}):
121121
d |= x #$ SPURIOUS: modification=d
122122
return d
123123

124-
# OK
125124
def sanitizer(l = []):
126125
if l:
127126
l.append(1)
127+
else:
128+
l.append(1) #$ modification=l
128129
return l
129130

130-
# OK
131131
def sanitizer_negated(l = [1]):
132132
if not l:
133133
l.append(1)
134+
else:
135+
l.append(1) #$ modification=l
134136
return l
135137

136-
# Not OK
137138
def sanitizer(l = []):
138139
if not l:
139140
l.append(1) #$ modification=l
141+
else:
142+
l.append(1) #$ SPURIOUS: modification=l
140143
return l
141144

142-
# Not OK
143145
def sanitizer_negated(l = [1]):
144146
if l:
145147
l.append(1) #$ modification=l
148+
else:
149+
l.append(1) #$ SPURIOUS: modification=l
146150
return l

0 commit comments

Comments
 (0)