Skip to content

Commit 78b3227

Browse files
committed
ruby: refine uninitialised loca
- there are places where uninitialised reads are intentional - there are also some places where they are impossible - require local data flow to further filter impossible reads of uninitialised values - provide context about uninitialised value for autofix
1 parent 92cfb6e commit 78b3227

File tree

1 file changed

+64
-5
lines changed

1 file changed

+64
-5
lines changed

ruby/ql/src/queries/variables/UninitializedLocal.ql

+64-5
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,80 @@
1212

1313
import codeql.ruby.AST
1414
import codeql.ruby.dataflow.SSA
15+
private import codeql.ruby.dataflow.internal.DataFlowPublic
16+
17+
predicate factor(Expr e, Expr factor) {
18+
factor = e
19+
or
20+
e.(BinaryOperation).getOperator() = ["||", "&&"] and
21+
factor = e.(BinaryOperation).getAnOperand()
22+
or
23+
e.(BinaryOperation).getOperator() = ["||", "&&"] and
24+
factor(e.(BinaryOperation).getAnOperand(), factor)
25+
}
26+
27+
predicate previousConjunct(Expr e, Expr prev) {
28+
exists(BinaryOperation b |
29+
b.getOperator() = "&&" and
30+
b.getRightOperand() = e
31+
|
32+
// 'prev' && 'e'
33+
prev = b.getLeftOperand()
34+
or
35+
// (... && 'prev') && 'e'
36+
b.getLeftOperand().(BinaryOperation).getOperator() = "&&" and
37+
prev = b.getLeftOperand().(BinaryOperation).getRightOperand()
38+
or
39+
// (subtree['prev'] && _) && 'e'
40+
b.getLeftOperand().(BinaryOperation).getOperator() = "&&" and
41+
previousConjunct(b.getLeftOperand().(BinaryOperation).getRightOperand(), prev)
42+
)
43+
}
44+
45+
Expr evaluatingMention(LocalVariableReadAccess read) {
46+
result = read
47+
or
48+
result.(AssignExpr).getLeftOperand() = read
49+
or
50+
result.(NotExpr).getOperand() = read
51+
}
1552

1653
class RelevantLocalVariableReadAccess extends LocalVariableReadAccess {
1754
RelevantLocalVariableReadAccess() {
1855
not exists(MethodCall c |
1956
c.getReceiver() = this and
2057
c.getMethodName() = "nil?"
58+
) and
59+
// 'a' is fine to be uninitialised in 'a || ...'
60+
not exists(BinaryOperation b |
61+
b.getLeftOperand() = this and
62+
b.getOperator() = "||"
63+
) and
64+
// The second 'a' cannot be uninitialised in 'a && (...a...)'
65+
not exists(Expr parent |
66+
parent.getAChild*() = this and
67+
previousConjunct(parent, this.getVariable().getAnAccess())
68+
) and
69+
// Various guards
70+
not exists(ConditionalExpr c | factor(c.getCondition(), evaluatingMention(this))) and
71+
not exists(ConditionalExpr c | factor(c.getCondition(), this.getVariable().getAnAccess()) |
72+
this = c.getBranch(true).getAChild*()
2173
)
2274
}
2375
}
2476

25-
from RelevantLocalVariableReadAccess read, LocalVariable v
77+
from RelevantLocalVariableReadAccess read, LocalVariable v, Node source
2678
where
2779
v = read.getVariable() and
28-
exists(Ssa::Definition def |
29-
def.getAnUltimateDefinition() instanceof Ssa::UninitializedDefinition and
30-
read = def.getARead().getExpr()
80+
exists(Ssa::Definition def, Ssa::UninitializedDefinition uninit, Node sink |
81+
uninit = def.getAnUltimateDefinition() and
82+
// def.getAnUltimateDefinition() instanceof Ssa::UninitializedDefinition and
83+
read = def.getARead().getExpr() and
84+
// localFlow(uninit, read)
85+
source.asExpr() = uninit.getARead() and
86+
sink.asExpr() = read.getAControlFlowNode() and
87+
localFlow(source, sink)
3188
)
32-
select read, "Local variable $@ may be used before it is initialized.", v, v.getName()
89+
select read,
90+
"Local variable $@ may be used before it is initialized. Uninitialized value appears $@.", v,
91+
v.getName(), source, "here"

0 commit comments

Comments
 (0)