@@ -4,50 +4,133 @@ import python
44import semmle.python.dataflow.new.internal.DataFlowDispatch
55import semmle.python.ApiGraphs
66
7- abstract class FileOpen extends DataFlow:: CfgNode { }
7+ /** A CFG node where a file is opened. */
8+ abstract class FileOpenSource extends DataFlow:: CfgNode { }
89
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+ }
1134}
1235
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 {
1438 FileOpen wrapped ;
1539
16- FileWrapperClassCall ( ) {
40+ FileWrapperCall ( ) {
1741 wrapped = this .getArg ( _) .getALocalSource ( ) and
1842 this .getFunction ( ) = classTracker ( _)
43+ or
44+ wrapped = this .getArg ( 0 ) and
45+ this = API:: moduleImport ( "os" ) .getMember ( "fdopen" ) .getACall ( )
1946 }
2047
48+ /** Gets the file that this call wraps. */
2149 FileOpen getWrapped ( ) { result = wrapped }
2250}
2351
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+ }
2566
67+ /** A call to the `.close()` method of a file object. */
2668class FileCloseCall extends FileClose {
2769 FileCloseCall ( ) { exists ( DataFlow:: MethodCallNode mc | mc .calls ( this , "close" ) ) }
2870}
2971
72+ /** A call to `os.close`. */
3073class OsCloseCall extends FileClose {
3174 OsCloseCall ( ) { this = API:: moduleImport ( "os" ) .getMember ( "close" ) .getACall ( ) .getArg ( 0 ) }
3275}
3376
77+ /** A `with` statement. */
3478class 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+ }
3688}
3789
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. */
3899predicate fileIsClosed ( FileOpen fo ) { exists ( FileClose fc | DataFlow:: localFlow ( fo , fc ) ) }
39100
101+ /** Holds if the file opened at `fo` is returned to the caller. This makes the caller responsible for closing the file. */
40102predicate 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+ )
42113}
43114
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. */
44116predicate fileIsStoredInField ( FileOpen fo ) {
45117 exists ( DataFlow:: AttrWrite aw | DataFlow:: localFlow ( fo , aw .getValue ( ) ) )
46118}
47119
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 ) {
49122 not fileIsClosed ( fo ) and
50123 not fileIsReturned ( fo ) and
51124 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+ )
53136}
0 commit comments