@@ -4,50 +4,133 @@ import python
4
4
import semmle.python.dataflow.new.internal.DataFlowDispatch
5
5
import semmle.python.ApiGraphs
6
6
7
- abstract class FileOpen extends DataFlow:: CfgNode { }
7
+ /** A CFG node where a file is opened. */
8
+ abstract class FileOpenSource extends DataFlow:: CfgNode { }
8
9
9
- class FileOpenCall extends FileOpen {
10
- FileOpenCall ( ) { this = [ API:: builtin ( "open" ) .getACall ( ) ] }
10
+ /** A call to the builtin `open` or `os.open`. */
11
+ class FileOpenCall extends FileOpenSource {
12
+ FileOpenCall ( ) {
13
+ this = [ API:: builtin ( "open" ) .getACall ( ) , API:: moduleImport ( "os" ) .getMember ( "open" ) .getACall ( ) ]
14
+ }
15
+ }
16
+
17
+ private DataFlow:: TypeTrackingNode fileOpenInstance ( DataFlow:: TypeTracker t ) {
18
+ t .start ( ) and
19
+ result instanceof FileOpenSource
20
+ or
21
+ exists ( DataFlow:: TypeTracker t2 | result = fileOpenInstance ( t2 ) .track ( t2 , t ) )
22
+ }
23
+
24
+ /** A call that returns an instance of an open file object. */
25
+ class FileOpen extends DataFlow:: CallCfgNode {
26
+ FileOpen ( ) { fileOpenInstance ( DataFlow:: TypeTracker:: end ( ) ) .flowsTo ( this ) }
27
+
28
+ /** Gets the local source of this file object, through any wrapper calls. */
29
+ FileOpen getLocalSource ( ) {
30
+ if this instanceof FileWrapperCall
31
+ then result = this .( FileWrapperCall ) .getWrapped ( ) .getLocalSource ( )
32
+ else result = this
33
+ }
11
34
}
12
35
13
- class FileWrapperClassCall extends FileOpen , DataFlow:: CallCfgNode {
36
+ /** A call that may wrap a file object in a wrapper class or `os.fdopen`. */
37
+ class FileWrapperCall extends FileOpenSource , DataFlow:: CallCfgNode {
14
38
FileOpen wrapped ;
15
39
16
- FileWrapperClassCall ( ) {
40
+ FileWrapperCall ( ) {
17
41
wrapped = this .getArg ( _) .getALocalSource ( ) and
18
42
this .getFunction ( ) = classTracker ( _)
43
+ or
44
+ wrapped = this .getArg ( 0 ) and
45
+ this = API:: moduleImport ( "os" ) .getMember ( "fdopen" ) .getACall ( )
19
46
}
20
47
48
+ /** Gets the file that this call wraps. */
21
49
FileOpen getWrapped ( ) { result = wrapped }
22
50
}
23
51
24
- abstract class FileClose extends DataFlow:: CfgNode { }
52
+ /** A node where a file is closed. */
53
+ abstract class FileClose extends DataFlow:: CfgNode {
54
+ /** Holds if this file close will occur if an exception is thrown at `e`. */
55
+ predicate guardsExceptions ( Expr e ) {
56
+ exists ( Try try |
57
+ e = try .getAStmt ( ) .getAChildNode * ( ) and
58
+ (
59
+ this .asExpr ( ) = try .getAHandler ( ) .getAChildNode * ( )
60
+ or
61
+ this .asExpr ( ) = try .getAFinalstmt ( ) .getAChildNode * ( )
62
+ )
63
+ )
64
+ }
65
+ }
25
66
67
+ /** A call to the `.close()` method of a file object. */
26
68
class FileCloseCall extends FileClose {
27
69
FileCloseCall ( ) { exists ( DataFlow:: MethodCallNode mc | mc .calls ( this , "close" ) ) }
28
70
}
29
71
72
+ /** A call to `os.close`. */
30
73
class OsCloseCall extends FileClose {
31
74
OsCloseCall ( ) { this = API:: moduleImport ( "os" ) .getMember ( "close" ) .getACall ( ) .getArg ( 0 ) }
32
75
}
33
76
77
+ /** A `with` statement. */
34
78
class WithStatement extends FileClose {
35
- WithStatement ( ) { exists ( With w | this .asExpr ( ) = w .getContextExpr ( ) ) }
79
+ With w ;
80
+
81
+ WithStatement ( ) { this .asExpr ( ) = w .getContextExpr ( ) }
82
+
83
+ override predicate guardsExceptions ( Expr e ) {
84
+ super .guardsExceptions ( e )
85
+ or
86
+ e = w .getAStmt ( ) .getAChildNode * ( )
87
+ }
36
88
}
37
89
90
+ /** Holds if an exception may be raised at `node` if it is a file object. */
91
+ private predicate mayRaiseWithFile ( DataFlow:: CfgNode node ) {
92
+ // Currently just consider any method called on `node`; e.g. `file.write()`; as potentially raising an exception
93
+ exists ( DataFlow:: MethodCallNode mc | node = mc .getObject ( ) ) and
94
+ not node instanceof FileOpen and
95
+ not node instanceof FileClose
96
+ }
97
+
98
+ /** Holds if the file opened at `fo` is closed. */
38
99
predicate fileIsClosed ( FileOpen fo ) { exists ( FileClose fc | DataFlow:: localFlow ( fo , fc ) ) }
39
100
101
+ /** Holds if the file opened at `fo` is returned to the caller. This makes the caller responsible for closing the file. */
40
102
predicate fileIsReturned ( FileOpen fo ) {
41
- exists ( Return ret | DataFlow:: localFlow ( fo , DataFlow:: exprNode ( ret .getValue ( ) ) ) )
103
+ exists ( Return ret , Expr retVal |
104
+ (
105
+ retVal = ret .getValue ( )
106
+ or
107
+ retVal = ret .getValue ( ) .( List ) .getAnElt ( )
108
+ or
109
+ retVal = ret .getValue ( ) .( Tuple ) .getAnElt ( )
110
+ ) and
111
+ DataFlow:: localFlow ( fo , DataFlow:: exprNode ( retVal ) )
112
+ )
42
113
}
43
114
115
+ /** Holds if the file opened at `fo` is stored in a field. We assume that another method is then responsible for closing the file. */
44
116
predicate fileIsStoredInField ( FileOpen fo ) {
45
117
exists ( DataFlow:: AttrWrite aw | DataFlow:: localFlow ( fo , aw .getValue ( ) ) )
46
118
}
47
119
48
- predicate fileNotAlwaysClosed ( FileOpen fo ) {
120
+ /** Holds if the file opened at `fo` is not closed, and is expected to be closed. */
121
+ predicate fileNotClosed ( FileOpen fo ) {
49
122
not fileIsClosed ( fo ) and
50
123
not fileIsReturned ( fo ) and
51
124
not fileIsStoredInField ( fo ) and
52
- not exists ( FileWrapperClassCall fwc | fo = fwc .getWrapped ( ) )
125
+ not exists ( FileWrapperCall fwc | fo = fwc .getWrapped ( ) )
126
+ }
127
+
128
+ predicate fileMayNotBeClosedOnException ( FileOpen fo , DataFlow:: Node raises ) {
129
+ fileIsClosed ( fo ) and
130
+ mayRaiseWithFile ( raises ) and
131
+ DataFlow:: localFlow ( fo , raises ) and
132
+ not exists ( FileClose fc |
133
+ DataFlow:: localFlow ( fo , fc ) and
134
+ fc .guardsExceptions ( raises .asExpr ( ) )
135
+ )
53
136
}
0 commit comments