| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent f8a0b1c commit b2acfbc
5 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4,13 +4,16 @@ | |||
| 4 | 4 | <qhelp> | |
| 5 | 5 | ||
| 6 | 6 | <overview> | |
| 7 | - <p>When a file is opened, it should always be closed. Failure to close files could result in loss of data or resource leaks.</p> | ||
| 8 | - | ||
| 7 | + <p>When a file is opened, it should always be closed. | ||
| 8 | + </p> | ||
| 9 | + <p>A file opened for writing that is not closed when the application exits may result in data loss, where not all of the data written may be saved to the file. | ||
| 10 | + A file opened for reading or writing that is not closed may also use up file descriptors, which is a resource leak that in long running applications could lead to a failure to open additional files. | ||
| 11 | + </p> | ||
| 9 | 12 | </overview> | |
| 10 | 13 | <recommendation> | |
| 11 | 14 | ||
| 12 | 15 | <p>Ensure that opened files are always closed, including when an exception could be raised. | |
| 13 | - The best practice is to use a <code>with</code> statement to automatically clean up resources. | ||
| 16 | + The best practice is often to use a <code>with</code> statement to automatically clean up resources. | ||
| 14 | 17 | Otherwise, ensure that <code>.close()</code> is called in a <code>try...except</code> or <code>try...finally</code> | |
| 15 | 18 | block to handle any possible exceptions. | |
| 16 | 19 | </p> | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -22,4 +22,4 @@ where | |||
| 22 | 22 | or | |
| 23 | 23 | fileMayNotBeClosedOnException(fo, _) and | |
| 24 | 24 | msg = "File may not be closed if an exception is raised." | |
| 25 | - select fo.getLocalSource(), msg | ||
| 25 | + select fo, msg | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -24,17 +24,10 @@ private DataFlow::TypeTrackingNode fileOpenInstance(DataFlow::TypeTracker t) { | |||
| 24 | 24 | /** A call that returns an instance of an open file object. */ | |
| 25 | 25 | class FileOpen extends DataFlow::CallCfgNode { | |
| 26 | 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 | - } | ||
| 34 | 27 | } | |
| 35 | 28 | ||
| 36 | 29 | /** A call that may wrap a file object in a wrapper class or `os.fdopen`. */ | |
| 37 | - class FileWrapperCall extends FileOpenSource, DataFlow::CallCfgNode { | ||
| 30 | + class FileWrapperCall extends DataFlow::CallCfgNode { | ||
| 38 | 31 | FileOpen wrapped; | |
| 39 | 32 | ||
| 40 | 33 | FileWrapperCall() { | |
@@ -53,14 +46,11 @@ class FileWrapperCall extends FileOpenSource, DataFlow::CallCfgNode { | |||
| 53 | 46 | abstract class FileClose extends DataFlow::CfgNode { | |
| 54 | 47 | /** Holds if this file close will occur if an exception is thrown at `e`. */ | |
| 55 | 48 | 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 | - ) | ||
| 49 | + this.asCfgNode() = | ||
| 50 | + DataFlow::exprNode(e).asCfgNode().getAnExceptionalSuccessor().getASuccessor*() | ||
| 51 | + or | ||
| 52 | + // the expression is after the close call | ||
| 53 | + DataFlow::exprNode(e).asCfgNode() = this.asCfgNode().getASuccessor*() | ||
| 64 | 54 | } | |
| 65 | 55 | } | |
| 66 | 56 | ||
@@ -95,8 +85,20 @@ private predicate mayRaiseWithFile(DataFlow::CfgNode node) { | |||
| 95 | 85 | not node instanceof FileClose | |
| 96 | 86 | } | |
| 97 | 87 | ||
| 88 | + /** Holds if data flows from `nodeFrom` to `nodeTo` in one step that also includes file wrapper classes. */ | ||
| 89 | + private predicate fileLocalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { | ||
| 90 | + DataFlow::localFlowStep(nodeFrom, nodeTo) | ||
| 91 | + or | ||
| 92 | + exists(FileWrapperCall fw | nodeFrom = fw.getWrapped() and nodeTo = fw) | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + /** Holds if data flows from `source` to `sink`, including file wrapper classes. */ | ||
| 96 | + private predicate fileLocalFlow(DataFlow::Node source, DataFlow::Node sink) { | ||
| 97 | + fileLocalFlowStep*(source, sink) | ||
| 98 | + } | ||
| 99 | + | ||
| 98 | 100 | /** Holds if the file opened at `fo` is closed. */ | |
| 99 | - predicate fileIsClosed(FileOpen fo) { exists(FileClose fc | DataFlow::localFlow(fo, fc)) } | ||
| 101 | + predicate fileIsClosed(FileOpen fo) { exists(FileClose fc | fileLocalFlow(fo, fc)) } | ||
| 100 | 102 | ||
| 101 | 103 | /** Holds if the file opened at `fo` is returned to the caller. This makes the caller responsible for closing the file. */ | |
| 102 | 104 | predicate fileIsReturned(FileOpen fo) { | |
@@ -108,27 +110,26 @@ predicate fileIsReturned(FileOpen fo) { | |||
| 108 | 110 | or | |
| 109 | 111 | retVal = ret.getValue().(Tuple).getAnElt() | |
| 110 | 112 | ) and | |
| 111 | - DataFlow::localFlow(fo, DataFlow::exprNode(retVal)) | ||
| 113 | + fileLocalFlow(fo, DataFlow::exprNode(retVal)) | ||
| 112 | 114 | ) | |
| 113 | 115 | } | |
| 114 | 116 | ||
| 115 | 117 | /** Holds if the file opened at `fo` is stored in a field. We assume that another method is then responsible for closing the file. */ | |
| 116 | 118 | predicate fileIsStoredInField(FileOpen fo) { | |
| 117 | - exists(DataFlow::AttrWrite aw | DataFlow::localFlow(fo, aw.getValue())) | ||
| 119 | + exists(DataFlow::AttrWrite aw | fileLocalFlow(fo, aw.getValue())) | ||
| 118 | 120 | } | |
| 119 | 121 | ||
| 120 | 122 | /** Holds if the file opened at `fo` is not closed, and is expected to be closed. */ | |
| 121 | 123 | predicate fileNotClosed(FileOpen fo) { | |
| 122 | 124 | not fileIsClosed(fo) and | |
| 123 | 125 | not fileIsReturned(fo) and | |
| 124 | - not fileIsStoredInField(fo) and | ||
| 125 | - not exists(FileWrapperCall fwc | fo = fwc.getWrapped()) | ||
| 126 | + not fileIsStoredInField(fo) | ||
| 126 | 127 | } | |
| 127 | 128 | ||
| 128 | 129 | predicate fileMayNotBeClosedOnException(FileOpen fo, DataFlow::Node raises) { | |
| 129 | 130 | fileIsClosed(fo) and | |
| 130 | 131 | mayRaiseWithFile(raises) and | |
| 131 | - DataFlow::localFlow(fo, raises) and | ||
| 132 | + fileLocalFlow(fo, raises) and | ||
| 132 | 133 | not exists(FileClose fc | | |
| 133 | 134 | DataFlow::localFlow(fo, fc) and | |
| 134 | 135 | fc.guardsExceptions(raises.asExpr()) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -46,10 +46,10 @@ def closed7(): | |||
| 46 | 46 | def not_closed8(): | |
| 47 | 47 | f8 = None | |
| 48 | 48 | try: | |
| 49 | - f8 = open("filename") # $ MISSING:notClosedOnException | ||
| 49 | + f8 = open("filename") # $ MISSING:notClosedOnException | ||
| 50 | 50 | f8.write("Error could occur") | |
| 51 | 51 | finally: | |
| 52 | - if f8 is None: | ||
| 52 | + if f8 is None: # We don't precisely consider this condition, so this result is MISSING. However, this seems uncommon. | ||
| 53 | 53 | f8.close() | |
| 54 | 54 | ||
| 55 | 55 | def not_closed9(): | |
@@ -58,7 +58,7 @@ def not_closed9(): | |||
| 58 | 58 | f9 = open("filename") # $ MISSING:notAlwaysClosed | |
| 59 | 59 | f9.write("Error could occur") | |
| 60 | 60 | finally: | |
| 61 | - if not f9: | ||
| 61 | + if not f9: # We don't precisely consider this condition, so this result is MISSING.However, this seems uncommon. | ||
| 62 | 62 | f9.close() | |
| 63 | 63 | ||
| 64 | 64 | def not_closed_but_cant_tell_locally(): | |
@@ -81,7 +81,7 @@ def not_closed11(): | |||
| 81 | 81 | f11.write("IOError could occur") | |
| 82 | 82 | f11.write("IOError could occur") | |
| 83 | 83 | f11.close() | |
| 84 | - except AttributeError: | ||
| 84 | + except AttributeError: # We don't consider the type of exception handled here, so this result is MISSING. | ||
| 85 | 85 | f11.close() | |
| 86 | 86 | ||
| 87 | 87 | def doesnt_raise(*args): | |
@@ -121,7 +121,7 @@ def closer2(t3): | |||
| 121 | 121 | ||
| 122 | 122 | def closed15(): | |
| 123 | 123 | f15 = opener_func2() # $ SPURIOUS:notClosed | |
| 124 | - closer2(f15) | ||
| 124 | + closer2(f15) # We don't detect that this call closes the file, so this result is SPURIOUS. | ||
| 125 | 125 | ||
| 126 | 126 | ||
| 127 | 127 | def may_not_be_closed16(name): | |
@@ -144,7 +144,7 @@ def not_closed17(): | |||
| 144 | 144 | f17.write("IOError could occur") | |
| 145 | 145 | may_raise("ValueError could occur") # FN here. | |
| 146 | 146 | f17.close() | |
| 147 | - except IOError: | ||
| 147 | + except IOError: # We don't detect that a ValueErrror could be raised that isn't handled here, so this result is MISSING. | ||
| 148 | 148 | f17.close() | |
| 149 | 149 | ||
| 150 | 150 | #ODASA-3779 | |
@@ -241,7 +241,7 @@ def not_closed22(path): | |||
| 241 | 241 | if foo: | |
| 242 | 242 | f22.close() | |
| 243 | 243 | finally: | |
| 244 | - if f22.closed: # Wrong sense | ||
| 244 | + if f22.closed: # We don't precisely consider this condition, so this result is MISSING. However, this seems uncommon. | ||
| 245 | 245 | f22.close() | |
| 246 | 246 | ||
| 247 | 247 | def not_closed23(path): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -7,7 +7,7 @@ module MethodArgTest implements TestSig { | |||
| 7 | 7 | ||
| 8 | 8 | predicate hasActualResult(Location location, string element, string tag, string value) { | |
| 9 | 9 | exists(DataFlow::CfgNode el, FileOpen fo | | |
| 10 | - el = fo.getLocalSource() and | ||
| 10 | + el = fo and | ||
| 11 | 11 | element = el.toString() and | |
| 12 | 12 | location = el.getLocation() and | |
| 13 | 13 | value = "" and | |
| Back | FazBrowse Home | New Git URL |
0 commit comments