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" 19 #include "clang/StaticAnalyzer/Core/Checker.h" 20 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 21 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 22 23 using namespace clang; 24 using namespace ento; 25 26 namespace { 27 typedef llvm::SmallVector<SymbolRef, 2> SymbolVector; 28 29 struct StreamState { 30 enum Kind { Opened, Closed } K; 31 32 StreamState(Kind InK) : K(InK) { } 33 34 bool isOpened() const { return K == Opened; } 35 bool isClosed() const { return K == Closed; } 36 37 static StreamState getOpened() { return StreamState(Opened); } 38 static StreamState getClosed() { return StreamState(Closed); } 39 40 bool operator==(const StreamState &X) const { 41 return K == X.K; 42 } 43 void Profile(llvm::FoldingSetNodeID &ID) const { 44 ID.AddInteger(K); 45 } 46 }; 47 48 class SimpleStreamChecker: public Checker<check::PostStmt<CallExpr>, 49 check::PreStmt<CallExpr>, 50 check::DeadSymbols, 51 eval::Assume > { 52 53 mutable IdentifierInfo *IIfopen, *IIfclose; 54 55 mutable OwningPtr<BugType> DoubleCloseBugType; 56 mutable OwningPtr<BugType> LeakBugType; 57 58 void initIdentifierInfo(ASTContext &Ctx) const; 59 60 void reportDoubleClose(SymbolRef FileDescSym, 61 const CallExpr *Call, 62 CheckerContext &C) const; 63 64 ExplodedNode *reportLeaks(SymbolVector LeakedStreams, 65 CheckerContext &C) const; 66 67 public: 68 SimpleStreamChecker() : IIfopen(0), IIfclose(0) {} 69 70 /// Process fopen. 71 void checkPostStmt(const CallExpr *Call, CheckerContext &C) const; 72 /// Process fclose. 73 void checkPreStmt(const CallExpr *Call, CheckerContext &C) const; 74 75 void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; 76 ProgramStateRef evalAssume(ProgramStateRef state, SVal Cond, 77 bool Assumption) const; 78 79 }; 80 81 } // end anonymous namespace 82 83 /// The state of the checker is a map from tracked stream symbols to their 84 /// state. Let's store it in the ProgramState. 85 REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) 86 87 void SimpleStreamChecker::checkPostStmt(const CallExpr *Call, 88 CheckerContext &C) const { 89 initIdentifierInfo(C.getASTContext()); 90 91 if (C.getCalleeIdentifier(Call) != IIfopen) 92 return; 93 94 // Get the symbolic value corresponding to the file handle. 95 SymbolRef FileDesc = C.getSVal(Call).getAsSymbol(); 96 if (!FileDesc) 97 return; 98 99 // Generate the next transition (an edge in the exploded graph). 100 ProgramStateRef State = C.getState(); 101 State = State->set<StreamMap>(FileDesc, StreamState::getOpened()); 102 C.addTransition(State); 103 } 104 105 void SimpleStreamChecker::checkPreStmt(const CallExpr *Call, 106 CheckerContext &C) const { 107 initIdentifierInfo(C.getASTContext()); 108 109 if (C.getCalleeIdentifier(Call) != IIfclose || Call->getNumArgs() != 1) 110 return; 111 112 // Get the symbolic value corresponding to the file handle. 113 SymbolRef FileDesc = C.getSVal(Call->getArg(0)).getAsSymbol(); 114 if (!FileDesc) 115 return; 116 117 // Check if the stream has already been closed. 118 ProgramStateRef State = C.getState(); 119 const StreamState *SS = State->get<StreamMap>(FileDesc); 120 if (SS && SS->isClosed()) 121 reportDoubleClose(FileDesc, Call, C); 122 123 // Generate the next transition, in which the stream is closed. 124 State = State->set<StreamMap>(FileDesc, StreamState::getClosed()); 125 C.addTransition(State); 126 } 127 128 void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, 129 CheckerContext &C) const { 130 ProgramStateRef State = C.getState(); 131 StreamMapTy TrackedStreams = State->get<StreamMap>(); 132 SymbolVector LeakedStreams; 133 for (StreamMapTy::iterator I = TrackedStreams.begin(), 134 E = TrackedStreams.end(); I != E; ++I) { 135 SymbolRef Sym = I->first; 136 if (SymReaper.isDead(Sym)) { 137 const StreamState &SS = I->second; 138 if (SS.isOpened()) 139 LeakedStreams.push_back(Sym); 140 141 // Remove the dead symbol from the streams map. 142 State = State->remove<StreamMap>(Sym); 143 } 144 } 145 146 ExplodedNode *N = reportLeaks(LeakedStreams, C); 147 C.addTransition(State, N); 148 } 149 150 // If a symbolic region is assumed to NULL (or another constant), stop tracking 151 // it - assuming that allocation failed on this path. 152 ProgramStateRef SimpleStreamChecker::evalAssume(ProgramStateRef State, 153 SVal Cond, 154 bool Assumption) const { 155 StreamMapTy TrackedStreams = State->get<StreamMap>(); 156 SymbolVector LeakedStreams; 157 for (StreamMapTy::iterator I = TrackedStreams.begin(), 158 E = TrackedStreams.end(); I != E; ++I) { 159 SymbolRef Sym = I->first; 160 if (State->getConstraintManager().isNull(State, Sym).isTrue()) 161 State = State->remove<StreamMap>(Sym); 162 } 163 return State; 164 } 165 166 void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym, 167 const CallExpr *CallExpr, 168 CheckerContext &C) const { 169 // We reached a bug, stop exploring the path here by generating a sink. 170 ExplodedNode *ErrNode = C.generateSink(); 171 // If this error node already exists, return. 172 if (!ErrNode) 173 return; 174 175 // Initialize the bug type. 176 if (!DoubleCloseBugType) 177 DoubleCloseBugType.reset(new BugType("Double fclose", 178 "Unix Stream API Error")); 179 // Generate the report. 180 BugReport *R = new BugReport(*DoubleCloseBugType, 181 "Closing a previously closed file stream", ErrNode); 182 R->addRange(CallExpr->getSourceRange()); 183 R->markInteresting(FileDescSym); 184 C.EmitReport(R); 185 } 186 187 ExplodedNode *SimpleStreamChecker::reportLeaks(SymbolVector LeakedStreams, 188 CheckerContext &C) const { 189 ExplodedNode *Pred = C.getPredecessor(); 190 if (LeakedStreams.empty()) 191 return Pred; 192 193 // Generate an intermediate node representing the leak point. 194 static SimpleProgramPointTag Tag("StreamChecker : Leak"); 195 ExplodedNode *ErrNode = C.addTransition(Pred->getState(), Pred, &Tag); 196 if (!ErrNode) 197 return Pred; 198 199 // Initialize the bug type. 200 if (!LeakBugType) { 201 LeakBugType.reset(new BuiltinBug("Resource Leak", 202 "Unix Stream API Error")); 203 // Sinks are higher importance bugs as well as calls to assert() or exit(0). 204 LeakBugType->setSuppressOnSink(true); 205 } 206 207 // Attach bug reports to the leak node. 208 for (llvm::SmallVector<SymbolRef, 2>::iterator 209 I = LeakedStreams.begin(), E = LeakedStreams.end(); I != E; ++I) { 210 BugReport *R = new BugReport(*LeakBugType, 211 "Opened file is never closed; potential resource leak", ErrNode); 212 C.EmitReport(R); 213 } 214 215 return ErrNode; 216 } 217 218 void SimpleStreamChecker::initIdentifierInfo(ASTContext &Ctx) const { 219 if (IIfopen) 220 return; 221 IIfopen = &Ctx.Idents.get("fopen"); 222 IIfclose = &Ctx.Idents.get("fclose"); 223 } 224 225 void ento::registerSimpleStreamChecker(CheckerManager &mgr) { 226 mgr.registerChecker<SimpleStreamChecker>(); 227 } 228