Skip to content

Commit 09694c4

Browse files
Rewrite file not closed simple case using dataflow
1 parent b096696 commit 09694c4

File tree

2 files changed

+97
-57
lines changed

2 files changed

+97
-57
lines changed

python/ql/src/Resources/FileNotAlwaysClosed.ql

Lines changed: 58 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -13,62 +13,63 @@
1313
*/
1414

1515
import python
16-
import FileOpen
17-
18-
/**
19-
* Whether resource is opened and closed in in a matched pair of methods,
20-
* either `__enter__` and `__exit__` or `__init__` and `__del__`
21-
*/
22-
predicate opened_in_enter_closed_in_exit(ControlFlowNode open) {
23-
file_not_closed_at_scope_exit(open) and
24-
exists(FunctionValue entry, FunctionValue exit |
25-
open.getScope() = entry.getScope() and
26-
exists(ClassValue cls |
27-
cls.declaredAttribute("__enter__") = entry and cls.declaredAttribute("__exit__") = exit
28-
or
29-
cls.declaredAttribute("__init__") = entry and cls.declaredAttribute("__del__") = exit
30-
) and
31-
exists(AttrNode attr_open, AttrNode attrclose |
32-
attr_open.getScope() = entry.getScope() and
33-
attrclose.getScope() = exit.getScope() and
34-
expr_is_open(attr_open.(DefinitionNode).getValue(), open) and
35-
attr_open.getName() = attrclose.getName() and
36-
close_method_call(_, attrclose)
37-
)
38-
)
39-
}
40-
41-
predicate file_not_closed_at_scope_exit(ControlFlowNode open) {
42-
exists(EssaVariable v |
43-
BaseFlow::reaches_exit(v) and
44-
var_is_open(v, open) and
45-
not file_is_returned(v, open)
46-
)
47-
or
48-
call_to_open(open) and
49-
not exists(AssignmentDefinition def | def.getValue() = open) and
50-
not exists(Return r | r.getValue() = open.getNode())
51-
}
52-
53-
predicate file_not_closed_at_exception_exit(ControlFlowNode open, ControlFlowNode exit) {
54-
exists(EssaVariable v |
55-
exit.(RaisingNode).viableExceptionalExit(_, _) and
56-
not closes_arg(exit, v.getSourceVariable()) and
57-
not close_method_call(exit, v.getAUse().(NameNode)) and
58-
var_is_open(v, open) and
59-
v.getAUse() = exit.getAChild*()
60-
)
61-
}
16+
// import FileOpen
17+
import FileNotAlwaysClosedQuery
6218

19+
// /**
20+
// * Whether resource is opened and closed in in a matched pair of methods,
21+
// * either `__enter__` and `__exit__` or `__init__` and `__del__`
22+
// */
23+
// predicate opened_in_enter_closed_in_exit(ControlFlowNode open) {
24+
// file_not_closed_at_scope_exit(open) and
25+
// exists(FunctionValue entry, FunctionValue exit |
26+
// open.getScope() = entry.getScope() and
27+
// exists(ClassValue cls |
28+
// cls.declaredAttribute("__enter__") = entry and cls.declaredAttribute("__exit__") = exit
29+
// or
30+
// cls.declaredAttribute("__init__") = entry and cls.declaredAttribute("__del__") = exit
31+
// ) and
32+
// exists(AttrNode attr_open, AttrNode attrclose |
33+
// attr_open.getScope() = entry.getScope() and
34+
// attrclose.getScope() = exit.getScope() and
35+
// expr_is_open(attr_open.(DefinitionNode).getValue(), open) and
36+
// attr_open.getName() = attrclose.getName() and
37+
// close_method_call(_, attrclose)
38+
// )
39+
// )
40+
// }
41+
// predicate file_not_closed_at_scope_exit(ControlFlowNode open) {
42+
// exists(EssaVariable v |
43+
// BaseFlow::reaches_exit(v) and
44+
// var_is_open(v, open) and
45+
// not file_is_returned(v, open)
46+
// )
47+
// or
48+
// call_to_open(open) and
49+
// not exists(AssignmentDefinition def | def.getValue() = open) and
50+
// not exists(Return r | r.getValue() = open.getNode())
51+
// }
52+
// predicate file_not_closed_at_exception_exit(ControlFlowNode open, ControlFlowNode exit) {
53+
// exists(EssaVariable v |
54+
// exit.(RaisingNode).viableExceptionalExit(_, _) and
55+
// not closes_arg(exit, v.getSourceVariable()) and
56+
// not close_method_call(exit, v.getAUse().(NameNode)) and
57+
// var_is_open(v, open) and
58+
// v.getAUse() = exit.getAChild*()
59+
// )
60+
// }
6361
/* Check to see if a file is opened but not closed or returned */
64-
from ControlFlowNode defn, string message
65-
where
66-
not opened_in_enter_closed_in_exit(defn) and
67-
(
68-
file_not_closed_at_scope_exit(defn) and message = "File is opened but is not closed."
69-
or
70-
not file_not_closed_at_scope_exit(defn) and
71-
file_not_closed_at_exception_exit(defn, _) and
72-
message = "File may not be closed if an exception is raised."
73-
)
74-
select defn.getNode(), message
62+
// from ControlFlowNode defn, string message
63+
// where
64+
// not opened_in_enter_closed_in_exit(defn) and
65+
// (
66+
// file_not_closed_at_scope_exit(defn) and message = "File is opened but is not closed."
67+
// or
68+
// not file_not_closed_at_scope_exit(defn) and
69+
// file_not_closed_at_exception_exit(defn, _) and
70+
// message = "File may not be closed if an exception is raised."
71+
// )
72+
// select defn.getNode(), message
73+
from FileOpen fo
74+
where fileNotAlwaysClosed(fo)
75+
select fo, "File is opened but is not closed."
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/** Definitions for reasoning about whether files are closed. */
2+
3+
import python
4+
//import semmle.python.dataflow.DataFlow
5+
import semmle.python.ApiGraphs
6+
7+
abstract class FileOpen extends DataFlow::CfgNode { }
8+
9+
class FileOpenCall extends FileOpen {
10+
FileOpenCall() { this = API::builtin("open").getACall() }
11+
}
12+
13+
// todo: type tracking to find wrapping funcs
14+
abstract class FileClose extends DataFlow::CfgNode { }
15+
16+
class FileCloseCall extends FileClose {
17+
FileCloseCall() { exists(DataFlow::MethodCallNode mc | mc.calls(this, "close")) }
18+
}
19+
20+
class WithStatement extends FileClose {
21+
WithStatement() { exists(With w | this.asExpr() = w.getContextExpr()) }
22+
}
23+
24+
predicate fileIsClosed(FileOpen fo) { exists(FileClose fc | DataFlow::localFlow(fo, fc)) }
25+
26+
predicate fileIsReturned(FileOpen fo) {
27+
exists(Return ret | DataFlow::localFlow(fo, DataFlow::exprNode(ret.getValue())))
28+
}
29+
30+
predicate fileIsStoredInField(FileOpen fo) {
31+
exists(DataFlow::AttrWrite aw | DataFlow::localFlow(fo, aw.getValue()))
32+
}
33+
34+
predicate fileNotAlwaysClosed(FileOpen fo) {
35+
not fileIsClosed(fo) and
36+
not fileIsReturned(fo) and
37+
not fileIsStoredInField(fo)
38+
// TODO: exception cases
39+
}

0 commit comments

Comments
 (0)