Skip to content

Commit 7cfa08a

Browse files
committed
Python: Do not use BarrierGuards
They are simply not right for this problem. We should not even make them available as an extension point.
1 parent b20232d commit 7cfa08a

File tree

4 files changed

+63
-72
lines changed

4 files changed

+63
-72
lines changed

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

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,14 @@ module ModificationOfParameterWithDefault {
3333

3434
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
3535

36-
override predicate isBarrier(DataFlow::Node node) { node instanceof Barrier }
37-
38-
override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
39-
// if we are tracking a empty default, then it is ok to modify truthy values,
36+
override predicate isBarrier(DataFlow::Node node) {
37+
// if we are tracking a non-empty default, then it is ok to modify empty values,
4038
// so our tracking ends at those.
41-
nonEmptyDefault = false and guard instanceof BlocksTruthy
39+
nonEmptyDefault = true and node instanceof MustBeEmpty
4240
or
43-
// if we are tracking a non-empty default, then it is ok to modify falsy values,
41+
// if we are tracking a empty default, then it is ok to modify non-empty values,
4442
// so our tracking ends at those.
45-
nonEmptyDefault = true and guard instanceof BlocksFalsey
43+
nonEmptyDefault = false and node instanceof MustBeNonEmpty
4644
}
4745
}
4846
}

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

Lines changed: 56 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -14,48 +14,45 @@ private import semmle.python.dataflow.new.BarrierGuards
1414
*/
1515
module ModificationOfParameterWithDefault {
1616
/**
17-
* A data flow source for detecting modifications of a parameters default value.
17+
* A data flow source for detecting modifications of a parameters default value,
18+
* that is a default value for some parameter.
1819
*/
1920
abstract class Source extends DataFlow::Node {
2021
/** Result is true if the default value is non-empty for this source and false if not. */
2122
abstract boolean isNonEmpty();
2223
}
2324

2425
/**
25-
* A data flow sink for detecting modifications of a parameters default value.
26+
* A data flow sink for detecting modifications of a parameters default value,
27+
* that is a node representing a modification.
2628
*/
2729
abstract class Sink extends DataFlow::Node { }
2830

2931
/**
30-
* A sanitizer for detecting modifications of a parameters default value.
31-
*/
32-
abstract class Barrier extends DataFlow::Node { }
33-
34-
/**
35-
* A sanitizer guard that does not let a truthy value flow to the true branch.
32+
* A sanitizer for detecting modifications of a parameters default value
33+
* should determine if the node (which is perhaps about to be modified)
34+
* can be the default value or not.
3635
*
37-
* Implementation note:
38-
* Since guards with different behaviour cannot exist on the same node,
39-
* we let all guards have the same behaviour, in the sense that they all check
40-
* the true branch. Instead, we partition guards into those that block
41-
* truthy values and those that block falsy values.
36+
* In this query we do not track the default value exactly, but rather wheter
37+
* it is empty or not (see `Source`).
4238
*
43-
* If you extend this class, make sure that your barrier checks the true branch.
39+
* This is the extension point for determining that a node must be empty and
40+
* therefor is allowed to be modified if the tracked default value is non-empty.
4441
*/
45-
abstract class BlocksTruthy extends DataFlow::BarrierGuard { }
42+
abstract class MustBeEmpty extends DataFlow::Node { }
4643

4744
/**
48-
* A sanitizer guard that does not let a falsy value flow to the true branch.
45+
* A sanitizer for detecting modifications of a parameters default value
46+
* should determine if the node (which is perhaps about to be modified)
47+
* can be the default value or not.
4948
*
50-
* Implementation note:
51-
* Since guards with different behaviour cannot exist on the same node,
52-
* we let all guards have the same behaviour, in the sense that they all check
53-
* the true branch. Instead, we partition guards into those that block
54-
* truthy values and those that block falsy values.
49+
* In this query we do not track the default value exactly, but rather wheter
50+
* it is empty or not (see `Source`).
5551
*
56-
* If you extend this class, make sure that your barrier checks the true branch.
52+
* This is the extension point for determining that a node must be non-empty
53+
* and therefor is allowed to be modified if the tracked default value is empty.
5754
*/
58-
abstract class BlocksFalsey extends DataFlow::BarrierGuard { }
55+
abstract class MustBeNonEmpty extends DataFlow::Node { }
5956

6057
/** Gets the truthiness (non emptyness) of the default of `p` if that value is mutable */
6158
private boolean mutableDefaultValue(Parameter p) {
@@ -140,46 +137,48 @@ module ModificationOfParameterWithDefault {
140137
}
141138
}
142139

143-
/**
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-
* ```
151-
*/
152-
class BlocksTruthyGuard extends BlocksTruthy {
153-
ControlFlowNode guarded;
154-
155-
BlocksTruthyGuard() { this instanceof NameNode }
156-
157-
override predicate checks(ControlFlowNode node, boolean branch) {
158-
node = this and
159-
branch = true
160-
}
161-
}
140+
// This to reimplement some of the functionality of the DataFlow::BarrierGuard
141+
private import semmle.python.essa.SsaCompute
162142

163143
/**
164-
* A simple sanitizer guard that does not let a truthy value flow to the true branch.
144+
* Simple detection of truthy and falsey values.
165145
*
166-
* Blocks flow of `x` in the true branch in the example below.
167-
* ```py
168-
* if not x:
169-
* x.append(42)
170-
* ```
146+
* It handles the cases `if x` and `if not x`.
171147
*/
172-
class BlocksFalseyGuard extends BlocksFalsey {
148+
private class MustBe extends DataFlow::Node {
149+
DataFlow::GuardNode guard;
173150
NameNode guarded;
174-
175-
BlocksFalseyGuard() {
176-
this.(UnaryExprNode).getNode().getOp() instanceof Not and
177-
guarded = this.(UnaryExprNode).getOperand()
151+
boolean branch;
152+
boolean truthy;
153+
154+
MustBe() {
155+
(
156+
// case: if x
157+
guard = guarded and
158+
branch = truthy
159+
or
160+
// case: if not x
161+
guard.(UnaryExprNode).getNode().getOp() instanceof Not and
162+
guarded = guard.(UnaryExprNode).getOperand() and
163+
branch = truthy.booleanNot()
164+
) and
165+
// guard controls this
166+
guard.controlsBlock(this.asCfgNode().getBasicBlock(), branch) and
167+
// there is a definition tying the guarded value to this
168+
exists(EssaDefinition def |
169+
AdjacentUses::useOfDef(def, this.asCfgNode()) and
170+
AdjacentUses::useOfDef(def, guarded)
171+
)
178172
}
173+
}
179174

180-
override predicate checks(ControlFlowNode node, boolean branch) {
181-
node = guarded and
182-
branch = true
183-
}
175+
/** Simple guard detecting truthy values. */
176+
private class MustBeTruthy extends MustBe, MustBeNonEmpty {
177+
MustBeTruthy() { truthy = true }
178+
}
179+
180+
/** Simple guard detecting falsey values. */
181+
private class MustBeFalsey extends MustBe, MustBeEmpty {
182+
MustBeFalsey() { truthy = false }
184183
}
185184
}

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

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,7 @@ edges
2727
| test.py:124:15:124:15 | ControlFlowNode for l | test.py:128:9:128:9 | ControlFlowNode for l |
2828
| test.py:131:23:131:23 | ControlFlowNode for l | test.py:135:9:135:9 | ControlFlowNode for l |
2929
| 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 |
3130
| 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 |
3331
nodes
3432
| test.py:2:12:2:12 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
3533
| test.py:3:5:3:5 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
@@ -81,10 +79,8 @@ nodes
8179
| test.py:135:9:135:9 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
8280
| test.py:138:15:138:15 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
8381
| 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 |
8582
| test.py:145:23:145:23 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
8683
| 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 |
8884
#select
8985
| 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 |
9086
| 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 |
@@ -108,6 +104,4 @@ nodes
108104
| 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 |
109105
| 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 |
110106
| 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 |
112107
| 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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,12 @@ def sanitizer(l = []):
139139
if not l:
140140
l.append(1) #$ modification=l
141141
else:
142-
l.append(1) #$ SPURIOUS: modification=l
142+
l.append(1)
143143
return l
144144

145145
def sanitizer_negated(l = [1]):
146146
if l:
147147
l.append(1) #$ modification=l
148148
else:
149-
l.append(1) #$ SPURIOUS: modification=l
149+
l.append(1)
150150
return l

0 commit comments

Comments
 (0)