Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 1 | //===-- SimpleStreamChecker.cpp -----------------------------------------*- C++ -*--// |
| 2 | // |
| 3 | // The LLVM Compiler Infrastructure |
| 4 | // |
| 5 | // This file is distributed under the University of Illinois Open Source |
| 6 | // License. See LICENSE.TXT for details. |
| 7 | // |
| 8 | //===----------------------------------------------------------------------===// |
| 9 | // |
| 10 | // Defines a checker for proper use of fopen/fclose APIs. |
| 11 | // - If a file has been closed with fclose, it should not be accessed again. |
| 12 | // Accessing a closed file results in undefined behavior. |
| 13 | // - If a file was opened with fopen, it must be closed with fclose before |
| 14 | // the execution ends. Failing to do so results in a resource leak. |
| 15 | // |
| 16 | //===----------------------------------------------------------------------===// |
| 17 | |
| 18 | #include "ClangSACheckers.h" |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 19 | #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
Chandler Carruth | 3a02247 | 2012-12-04 09:13:33 +0000 | [diff] [blame] | 20 | #include "clang/StaticAnalyzer/Core/Checker.h" |
Jordan Rose | 58e8293 | 2012-11-02 23:49:35 +0000 | [diff] [blame] | 21 | #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 22 | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
Benjamin Kramer | cfeacf5 | 2016-05-27 14:27:13 +0000 | [diff] [blame^] | 23 | #include <utility> |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 24 | |
| 25 | using namespace clang; |
| 26 | using namespace ento; |
| 27 | |
| 28 | namespace { |
Dmitri Gribenko | f857950 | 2013-01-12 19:30:44 +0000 | [diff] [blame] | 29 | typedef SmallVector<SymbolRef, 2> SymbolVector; |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 30 | |
| 31 | struct StreamState { |
Anna Zaks | 1e80d8b | 2012-10-31 02:32:41 +0000 | [diff] [blame] | 32 | private: |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 33 | enum Kind { Opened, Closed } K; |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 34 | StreamState(Kind InK) : K(InK) { } |
| 35 | |
Anna Zaks | 1e80d8b | 2012-10-31 02:32:41 +0000 | [diff] [blame] | 36 | public: |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 37 | bool isOpened() const { return K == Opened; } |
| 38 | bool isClosed() const { return K == Closed; } |
| 39 | |
| 40 | static StreamState getOpened() { return StreamState(Opened); } |
| 41 | static StreamState getClosed() { return StreamState(Closed); } |
| 42 | |
| 43 | bool operator==(const StreamState &X) const { |
| 44 | return K == X.K; |
| 45 | } |
| 46 | void Profile(llvm::FoldingSetNodeID &ID) const { |
| 47 | ID.AddInteger(K); |
| 48 | } |
| 49 | }; |
| 50 | |
Jordan Rose | 58e8293 | 2012-11-02 23:49:35 +0000 | [diff] [blame] | 51 | class SimpleStreamChecker : public Checker<check::PostCall, |
| 52 | check::PreCall, |
Anna Zaks | 2ed5125 | 2012-11-06 04:20:57 +0000 | [diff] [blame] | 53 | check::DeadSymbols, |
Anna Zaks | 0dffbd6 | 2012-12-22 00:18:39 +0000 | [diff] [blame] | 54 | check::PointerEscape> { |
Gabor Horvath | 343730c | 2016-01-22 22:32:46 +0000 | [diff] [blame] | 55 | CallDescription OpenFn, CloseFn; |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 56 | |
Ahmed Charles | b898432 | 2014-03-07 20:03:18 +0000 | [diff] [blame] | 57 | std::unique_ptr<BugType> DoubleCloseBugType; |
| 58 | std::unique_ptr<BugType> LeakBugType; |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 59 | |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 60 | void reportDoubleClose(SymbolRef FileDescSym, |
Jordan Rose | 58e8293 | 2012-11-02 23:49:35 +0000 | [diff] [blame] | 61 | const CallEvent &Call, |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 62 | CheckerContext &C) const; |
| 63 | |
Benjamin Kramer | 22c68ef | 2014-09-11 14:13:49 +0000 | [diff] [blame] | 64 | void reportLeaks(ArrayRef<SymbolRef> LeakedStreams, CheckerContext &C, |
Jordan Rose | 2b21372 | 2012-11-01 00:18:41 +0000 | [diff] [blame] | 65 | ExplodedNode *ErrNode) const; |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 66 | |
Anna Zaks | 2ed5125 | 2012-11-06 04:20:57 +0000 | [diff] [blame] | 67 | bool guaranteedNotToCloseFile(const CallEvent &Call) const; |
| 68 | |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 69 | public: |
Anna Zaks | 1e80d8b | 2012-10-31 02:32:41 +0000 | [diff] [blame] | 70 | SimpleStreamChecker(); |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 71 | |
| 72 | /// Process fopen. |
Jordan Rose | 58e8293 | 2012-11-02 23:49:35 +0000 | [diff] [blame] | 73 | void checkPostCall(const CallEvent &Call, CheckerContext &C) const; |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 74 | /// Process fclose. |
Jordan Rose | 58e8293 | 2012-11-02 23:49:35 +0000 | [diff] [blame] | 75 | void checkPreCall(const CallEvent &Call, CheckerContext &C) const; |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 76 | |
| 77 | void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; |
Anna Zaks | 2ed5125 | 2012-11-06 04:20:57 +0000 | [diff] [blame] | 78 | |
Anna Zaks | 0dffbd6 | 2012-12-22 00:18:39 +0000 | [diff] [blame] | 79 | /// Stop tracking addresses which escape. |
| 80 | ProgramStateRef checkPointerEscape(ProgramStateRef State, |
| 81 | const InvalidatedSymbols &Escaped, |
Anna Zaks | acdc13c | 2013-02-07 23:05:43 +0000 | [diff] [blame] | 82 | const CallEvent *Call, |
| 83 | PointerEscapeKind Kind) const; |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 84 | }; |
| 85 | |
| 86 | } // end anonymous namespace |
| 87 | |
| 88 | /// The state of the checker is a map from tracked stream symbols to their |
Anna Zaks | 4afaaf2 | 2012-10-30 04:17:18 +0000 | [diff] [blame] | 89 | /// state. Let's store it in the ProgramState. |
| 90 | REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 91 | |
Anna Zaks | 2ed5125 | 2012-11-06 04:20:57 +0000 | [diff] [blame] | 92 | namespace { |
David Blaikie | 903c293 | 2015-08-13 22:50:09 +0000 | [diff] [blame] | 93 | class StopTrackingCallback final : public SymbolVisitor { |
Anna Zaks | 2ed5125 | 2012-11-06 04:20:57 +0000 | [diff] [blame] | 94 | ProgramStateRef state; |
| 95 | public: |
Benjamin Kramer | cfeacf5 | 2016-05-27 14:27:13 +0000 | [diff] [blame^] | 96 | StopTrackingCallback(ProgramStateRef st) : state(std::move(st)) {} |
Anna Zaks | 2ed5125 | 2012-11-06 04:20:57 +0000 | [diff] [blame] | 97 | ProgramStateRef getState() const { return state; } |
| 98 | |
Craig Topper | fb6b25b | 2014-03-15 04:29:04 +0000 | [diff] [blame] | 99 | bool VisitSymbol(SymbolRef sym) override { |
Anna Zaks | 2ed5125 | 2012-11-06 04:20:57 +0000 | [diff] [blame] | 100 | state = state->remove<StreamMap>(sym); |
| 101 | return true; |
| 102 | } |
| 103 | }; |
| 104 | } // end anonymous namespace |
| 105 | |
Craig Topper | 0dbb783 | 2014-05-27 02:45:47 +0000 | [diff] [blame] | 106 | SimpleStreamChecker::SimpleStreamChecker() |
Gabor Horvath | 343730c | 2016-01-22 22:32:46 +0000 | [diff] [blame] | 107 | : OpenFn("fopen"), CloseFn("fclose", 1) { |
Anna Zaks | 1e80d8b | 2012-10-31 02:32:41 +0000 | [diff] [blame] | 108 | // Initialize the bug types. |
Alexander Kornienko | 4aca9b1 | 2014-02-11 21:49:21 +0000 | [diff] [blame] | 109 | DoubleCloseBugType.reset( |
| 110 | new BugType(this, "Double fclose", "Unix Stream API Error")); |
Anna Zaks | 1e80d8b | 2012-10-31 02:32:41 +0000 | [diff] [blame] | 111 | |
Alexander Kornienko | 4aca9b1 | 2014-02-11 21:49:21 +0000 | [diff] [blame] | 112 | LeakBugType.reset( |
| 113 | new BugType(this, "Resource Leak", "Unix Stream API Error")); |
Anna Zaks | 1e80d8b | 2012-10-31 02:32:41 +0000 | [diff] [blame] | 114 | // Sinks are higher importance bugs as well as calls to assert() or exit(0). |
| 115 | LeakBugType->setSuppressOnSink(true); |
| 116 | } |
| 117 | |
Jordan Rose | 58e8293 | 2012-11-02 23:49:35 +0000 | [diff] [blame] | 118 | void SimpleStreamChecker::checkPostCall(const CallEvent &Call, |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 119 | CheckerContext &C) const { |
Jordan Rose | 58e8293 | 2012-11-02 23:49:35 +0000 | [diff] [blame] | 120 | if (!Call.isGlobalCFunction()) |
| 121 | return; |
| 122 | |
Gabor Horvath | 343730c | 2016-01-22 22:32:46 +0000 | [diff] [blame] | 123 | if (!Call.isCalled(OpenFn)) |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 124 | return; |
| 125 | |
| 126 | // Get the symbolic value corresponding to the file handle. |
Jordan Rose | 58e8293 | 2012-11-02 23:49:35 +0000 | [diff] [blame] | 127 | SymbolRef FileDesc = Call.getReturnValue().getAsSymbol(); |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 128 | if (!FileDesc) |
| 129 | return; |
| 130 | |
| 131 | // Generate the next transition (an edge in the exploded graph). |
| 132 | ProgramStateRef State = C.getState(); |
| 133 | State = State->set<StreamMap>(FileDesc, StreamState::getOpened()); |
| 134 | C.addTransition(State); |
| 135 | } |
| 136 | |
Jordan Rose | 58e8293 | 2012-11-02 23:49:35 +0000 | [diff] [blame] | 137 | void SimpleStreamChecker::checkPreCall(const CallEvent &Call, |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 138 | CheckerContext &C) const { |
Jordan Rose | 58e8293 | 2012-11-02 23:49:35 +0000 | [diff] [blame] | 139 | if (!Call.isGlobalCFunction()) |
| 140 | return; |
| 141 | |
Gabor Horvath | 343730c | 2016-01-22 22:32:46 +0000 | [diff] [blame] | 142 | if (!Call.isCalled(CloseFn)) |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 143 | return; |
| 144 | |
| 145 | // Get the symbolic value corresponding to the file handle. |
Jordan Rose | 58e8293 | 2012-11-02 23:49:35 +0000 | [diff] [blame] | 146 | SymbolRef FileDesc = Call.getArgSVal(0).getAsSymbol(); |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 147 | if (!FileDesc) |
| 148 | return; |
| 149 | |
| 150 | // Check if the stream has already been closed. |
| 151 | ProgramStateRef State = C.getState(); |
| 152 | const StreamState *SS = State->get<StreamMap>(FileDesc); |
Anna Zaks | a57e8ff | 2012-10-31 22:17:48 +0000 | [diff] [blame] | 153 | if (SS && SS->isClosed()) { |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 154 | reportDoubleClose(FileDesc, Call, C); |
Anna Zaks | a57e8ff | 2012-10-31 22:17:48 +0000 | [diff] [blame] | 155 | return; |
| 156 | } |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 157 | |
| 158 | // Generate the next transition, in which the stream is closed. |
| 159 | State = State->set<StreamMap>(FileDesc, StreamState::getClosed()); |
| 160 | C.addTransition(State); |
| 161 | } |
| 162 | |
Anna Zaks | da27efe | 2012-11-02 21:30:04 +0000 | [diff] [blame] | 163 | static bool isLeaked(SymbolRef Sym, const StreamState &SS, |
| 164 | bool IsSymDead, ProgramStateRef State) { |
| 165 | if (IsSymDead && SS.isOpened()) { |
| 166 | // If a symbol is NULL, assume that fopen failed on this path. |
| 167 | // A symbol should only be considered leaked if it is non-null. |
| 168 | ConstraintManager &CMgr = State->getConstraintManager(); |
| 169 | ConditionTruthVal OpenFailed = CMgr.isNull(State, Sym); |
| 170 | return !OpenFailed.isConstrainedTrue(); |
| 171 | } |
| 172 | return false; |
| 173 | } |
| 174 | |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 175 | void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, |
| 176 | CheckerContext &C) const { |
| 177 | ProgramStateRef State = C.getState(); |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 178 | SymbolVector LeakedStreams; |
Anna Zaks | da27efe | 2012-11-02 21:30:04 +0000 | [diff] [blame] | 179 | StreamMapTy TrackedStreams = State->get<StreamMap>(); |
Anna Zaks | 302da83 | 2012-10-30 04:17:40 +0000 | [diff] [blame] | 180 | for (StreamMapTy::iterator I = TrackedStreams.begin(), |
Jordan Rose | 14fe9f3 | 2012-11-01 00:18:27 +0000 | [diff] [blame] | 181 | E = TrackedStreams.end(); I != E; ++I) { |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 182 | SymbolRef Sym = I->first; |
Anna Zaks | da27efe | 2012-11-02 21:30:04 +0000 | [diff] [blame] | 183 | bool IsSymDead = SymReaper.isDead(Sym); |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 184 | |
Anna Zaks | da27efe | 2012-11-02 21:30:04 +0000 | [diff] [blame] | 185 | // Collect leaked symbols. |
| 186 | if (isLeaked(Sym, I->second, IsSymDead, State)) |
| 187 | LeakedStreams.push_back(Sym); |
| 188 | |
| 189 | // Remove the dead symbol from the streams map. |
| 190 | if (IsSymDead) |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 191 | State = State->remove<StreamMap>(Sym); |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 192 | } |
| 193 | |
Devin Coughlin | e39bd40 | 2015-09-16 22:03:05 +0000 | [diff] [blame] | 194 | ExplodedNode *N = C.generateNonFatalErrorNode(State); |
| 195 | if (!N) |
| 196 | return; |
Anna Zaks | 1e80d8b | 2012-10-31 02:32:41 +0000 | [diff] [blame] | 197 | reportLeaks(LeakedStreams, C, N); |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 198 | } |
| 199 | |
| 200 | void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym, |
Jordan Rose | 58e8293 | 2012-11-02 23:49:35 +0000 | [diff] [blame] | 201 | const CallEvent &Call, |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 202 | CheckerContext &C) const { |
| 203 | // We reached a bug, stop exploring the path here by generating a sink. |
Devin Coughlin | e39bd40 | 2015-09-16 22:03:05 +0000 | [diff] [blame] | 204 | ExplodedNode *ErrNode = C.generateErrorNode(); |
Anna Zaks | 1e80d8b | 2012-10-31 02:32:41 +0000 | [diff] [blame] | 205 | // If we've already reached this node on another path, return. |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 206 | if (!ErrNode) |
| 207 | return; |
| 208 | |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 209 | // Generate the report. |
Aaron Ballman | 8d3a7a5 | 2015-06-23 13:15:32 +0000 | [diff] [blame] | 210 | auto R = llvm::make_unique<BugReport>(*DoubleCloseBugType, |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 211 | "Closing a previously closed file stream", ErrNode); |
Jordan Rose | 58e8293 | 2012-11-02 23:49:35 +0000 | [diff] [blame] | 212 | R->addRange(Call.getSourceRange()); |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 213 | R->markInteresting(FileDescSym); |
Aaron Ballman | 8d3a7a5 | 2015-06-23 13:15:32 +0000 | [diff] [blame] | 214 | C.emitReport(std::move(R)); |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 215 | } |
| 216 | |
Benjamin Kramer | 22c68ef | 2014-09-11 14:13:49 +0000 | [diff] [blame] | 217 | void SimpleStreamChecker::reportLeaks(ArrayRef<SymbolRef> LeakedStreams, |
| 218 | CheckerContext &C, |
| 219 | ExplodedNode *ErrNode) const { |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 220 | // Attach bug reports to the leak node. |
Anna Zaks | 92d9660 | 2012-10-30 04:18:21 +0000 | [diff] [blame] | 221 | // TODO: Identify the leaked file descriptor. |
Benjamin Kramer | 22c68ef | 2014-09-11 14:13:49 +0000 | [diff] [blame] | 222 | for (SymbolRef LeakedStream : LeakedStreams) { |
Aaron Ballman | 8d3a7a5 | 2015-06-23 13:15:32 +0000 | [diff] [blame] | 223 | auto R = llvm::make_unique<BugReport>(*LeakBugType, |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 224 | "Opened file is never closed; potential resource leak", ErrNode); |
Benjamin Kramer | 22c68ef | 2014-09-11 14:13:49 +0000 | [diff] [blame] | 225 | R->markInteresting(LeakedStream); |
Aaron Ballman | 8d3a7a5 | 2015-06-23 13:15:32 +0000 | [diff] [blame] | 226 | C.emitReport(std::move(R)); |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 227 | } |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 228 | } |
| 229 | |
Anna Zaks | 2ed5125 | 2012-11-06 04:20:57 +0000 | [diff] [blame] | 230 | bool SimpleStreamChecker::guaranteedNotToCloseFile(const CallEvent &Call) const{ |
| 231 | // If it's not in a system header, assume it might close a file. |
| 232 | if (!Call.isInSystemHeader()) |
| 233 | return false; |
| 234 | |
| 235 | // Handle cases where we know a buffer's /address/ can escape. |
| 236 | if (Call.argumentsMayEscape()) |
| 237 | return false; |
| 238 | |
| 239 | // Note, even though fclose closes the file, we do not list it here |
| 240 | // since the checker is modeling the call. |
| 241 | |
| 242 | return true; |
| 243 | } |
| 244 | |
Anna Zaks | 0dffbd6 | 2012-12-22 00:18:39 +0000 | [diff] [blame] | 245 | // If the pointer we are tracking escaped, do not track the symbol as |
Anna Zaks | 2ed5125 | 2012-11-06 04:20:57 +0000 | [diff] [blame] | 246 | // we cannot reason about it anymore. |
| 247 | ProgramStateRef |
Anna Zaks | 0dffbd6 | 2012-12-22 00:18:39 +0000 | [diff] [blame] | 248 | SimpleStreamChecker::checkPointerEscape(ProgramStateRef State, |
| 249 | const InvalidatedSymbols &Escaped, |
Anna Zaks | acdc13c | 2013-02-07 23:05:43 +0000 | [diff] [blame] | 250 | const CallEvent *Call, |
| 251 | PointerEscapeKind Kind) const { |
Anna Zaks | 0dffbd6 | 2012-12-22 00:18:39 +0000 | [diff] [blame] | 252 | // If we know that the call cannot close a file, there is nothing to do. |
Jordan Rose | 757fbb0 | 2013-05-10 17:07:16 +0000 | [diff] [blame] | 253 | if (Kind == PSK_DirectEscapeOnCall && guaranteedNotToCloseFile(*Call)) { |
Anna Zaks | 2ed5125 | 2012-11-06 04:20:57 +0000 | [diff] [blame] | 254 | return State; |
Anna Zaks | acdc13c | 2013-02-07 23:05:43 +0000 | [diff] [blame] | 255 | } |
Anna Zaks | 2ed5125 | 2012-11-06 04:20:57 +0000 | [diff] [blame] | 256 | |
Anna Zaks | 0dffbd6 | 2012-12-22 00:18:39 +0000 | [diff] [blame] | 257 | for (InvalidatedSymbols::const_iterator I = Escaped.begin(), |
| 258 | E = Escaped.end(); |
| 259 | I != E; ++I) { |
| 260 | SymbolRef Sym = *I; |
Anna Zaks | 2ed5125 | 2012-11-06 04:20:57 +0000 | [diff] [blame] | 261 | |
Anna Zaks | 2ed5125 | 2012-11-06 04:20:57 +0000 | [diff] [blame] | 262 | // The symbol escaped. Optimistically, assume that the corresponding file |
| 263 | // handle will be closed somewhere else. |
Anna Zaks | 0dffbd6 | 2012-12-22 00:18:39 +0000 | [diff] [blame] | 264 | State = State->remove<StreamMap>(Sym); |
Anna Zaks | 2ed5125 | 2012-11-06 04:20:57 +0000 | [diff] [blame] | 265 | } |
| 266 | return State; |
| 267 | } |
| 268 | |
Anna Zaks | be70d4d | 2012-10-29 22:51:50 +0000 | [diff] [blame] | 269 | void ento::registerSimpleStreamChecker(CheckerManager &mgr) { |
| 270 | mgr.registerChecker<SimpleStreamChecker>(); |
| 271 | } |