blob: 203271afcb435a2847a43ac6169a7386495a5cb8 [file] [log] [blame]
Anna Zaksd65e55d2012-10-29 22:51:50 +00001//===-- 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
23using namespace clang;
24using namespace ento;
25
26namespace {
27typedef llvm::SmallVector<SymbolRef, 2> SymbolVector;
28
29struct StreamState {
Anna Zaks32133cf2012-10-31 02:32:41 +000030private:
Anna Zaksd65e55d2012-10-29 22:51:50 +000031 enum Kind { Opened, Closed } K;
Anna Zaksd65e55d2012-10-29 22:51:50 +000032 StreamState(Kind InK) : K(InK) { }
33
Anna Zaks32133cf2012-10-31 02:32:41 +000034public:
Anna Zaksd65e55d2012-10-29 22:51:50 +000035 bool isOpened() const { return K == Opened; }
36 bool isClosed() const { return K == Closed; }
37
38 static StreamState getOpened() { return StreamState(Opened); }
39 static StreamState getClosed() { return StreamState(Closed); }
40
41 bool operator==(const StreamState &X) const {
42 return K == X.K;
43 }
44 void Profile(llvm::FoldingSetNodeID &ID) const {
45 ID.AddInteger(K);
46 }
47};
48
Jordan Rose32f38c12012-11-01 00:18:41 +000049class SimpleStreamChecker : public Checker<check::PostStmt<CallExpr>,
50 check::PreStmt<CallExpr>,
51 check::DeadSymbols > {
Anna Zaksd65e55d2012-10-29 22:51:50 +000052
53 mutable IdentifierInfo *IIfopen, *IIfclose;
54
Jordan Rose32f38c12012-11-01 00:18:41 +000055 OwningPtr<BugType> DoubleCloseBugType;
56 OwningPtr<BugType> LeakBugType;
Anna Zaksd65e55d2012-10-29 22:51:50 +000057
58 void initIdentifierInfo(ASTContext &Ctx) const;
59
60 void reportDoubleClose(SymbolRef FileDescSym,
61 const CallExpr *Call,
62 CheckerContext &C) const;
63
Jordan Rose32f38c12012-11-01 00:18:41 +000064 void reportLeaks(SymbolVector LeakedStreams,
65 CheckerContext &C,
66 ExplodedNode *ErrNode) const;
Anna Zaksd65e55d2012-10-29 22:51:50 +000067
68public:
Anna Zaks32133cf2012-10-31 02:32:41 +000069 SimpleStreamChecker();
Anna Zaksd65e55d2012-10-29 22:51:50 +000070
71 /// Process fopen.
72 void checkPostStmt(const CallExpr *Call, CheckerContext &C) const;
73 /// Process fclose.
74 void checkPreStmt(const CallExpr *Call, CheckerContext &C) const;
75
76 void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
Anna Zaksd65e55d2012-10-29 22:51:50 +000077};
78
79} // end anonymous namespace
80
81/// The state of the checker is a map from tracked stream symbols to their
Anna Zaksac150f22012-10-30 04:17:18 +000082/// state. Let's store it in the ProgramState.
83REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
Anna Zaksd65e55d2012-10-29 22:51:50 +000084
Anna Zaks32133cf2012-10-31 02:32:41 +000085SimpleStreamChecker::SimpleStreamChecker() : IIfopen(0), IIfclose(0) {
86 // Initialize the bug types.
87 DoubleCloseBugType.reset(new BugType("Double fclose",
88 "Unix Stream API Error"));
89
90 LeakBugType.reset(new BugType("Resource Leak",
91 "Unix Stream API Error"));
92 // Sinks are higher importance bugs as well as calls to assert() or exit(0).
93 LeakBugType->setSuppressOnSink(true);
94}
95
Anna Zaksd65e55d2012-10-29 22:51:50 +000096void SimpleStreamChecker::checkPostStmt(const CallExpr *Call,
97 CheckerContext &C) const {
98 initIdentifierInfo(C.getASTContext());
99
100 if (C.getCalleeIdentifier(Call) != IIfopen)
101 return;
102
103 // Get the symbolic value corresponding to the file handle.
104 SymbolRef FileDesc = C.getSVal(Call).getAsSymbol();
105 if (!FileDesc)
106 return;
107
108 // Generate the next transition (an edge in the exploded graph).
109 ProgramStateRef State = C.getState();
110 State = State->set<StreamMap>(FileDesc, StreamState::getOpened());
111 C.addTransition(State);
112}
113
114void SimpleStreamChecker::checkPreStmt(const CallExpr *Call,
115 CheckerContext &C) const {
116 initIdentifierInfo(C.getASTContext());
117
Anna Zaks360b29c2012-10-30 04:17:40 +0000118 if (C.getCalleeIdentifier(Call) != IIfclose || Call->getNumArgs() != 1)
Anna Zaksd65e55d2012-10-29 22:51:50 +0000119 return;
120
121 // Get the symbolic value corresponding to the file handle.
122 SymbolRef FileDesc = C.getSVal(Call->getArg(0)).getAsSymbol();
123 if (!FileDesc)
124 return;
125
126 // Check if the stream has already been closed.
127 ProgramStateRef State = C.getState();
128 const StreamState *SS = State->get<StreamMap>(FileDesc);
Anna Zaksbbb751a2012-10-31 22:17:48 +0000129 if (SS && SS->isClosed()) {
Anna Zaksd65e55d2012-10-29 22:51:50 +0000130 reportDoubleClose(FileDesc, Call, C);
Anna Zaksbbb751a2012-10-31 22:17:48 +0000131 return;
132 }
Anna Zaksd65e55d2012-10-29 22:51:50 +0000133
134 // Generate the next transition, in which the stream is closed.
135 State = State->set<StreamMap>(FileDesc, StreamState::getClosed());
136 C.addTransition(State);
137}
138
139void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
140 CheckerContext &C) const {
141 ProgramStateRef State = C.getState();
Anna Zaks360b29c2012-10-30 04:17:40 +0000142 StreamMapTy TrackedStreams = State->get<StreamMap>();
Jordan Roseec8d4202012-11-01 00:18:27 +0000143
Anna Zaksd65e55d2012-10-29 22:51:50 +0000144 SymbolVector LeakedStreams;
Anna Zaks360b29c2012-10-30 04:17:40 +0000145 for (StreamMapTy::iterator I = TrackedStreams.begin(),
Jordan Roseec8d4202012-11-01 00:18:27 +0000146 E = TrackedStreams.end(); I != E; ++I) {
Anna Zaksd65e55d2012-10-29 22:51:50 +0000147 SymbolRef Sym = I->first;
148 if (SymReaper.isDead(Sym)) {
149 const StreamState &SS = I->second;
Anna Zaks32133cf2012-10-31 02:32:41 +0000150 if (SS.isOpened()) {
Jordan Roseec8d4202012-11-01 00:18:27 +0000151 // If a symbol is NULL, assume that fopen failed on this path
152 // and do not report a leak.
153 ConstraintManager &CMgr = State->getConstraintManager();
154 ConditionTruthVal OpenFailed = CMgr.isNull(State, Sym);
155 if (!OpenFailed.isConstrainedTrue())
Anna Zaks32133cf2012-10-31 02:32:41 +0000156 LeakedStreams.push_back(Sym);
157 }
Anna Zaksd65e55d2012-10-29 22:51:50 +0000158
159 // Remove the dead symbol from the streams map.
160 State = State->remove<StreamMap>(Sym);
161 }
162 }
163
Anna Zaks32133cf2012-10-31 02:32:41 +0000164 ExplodedNode *N = C.addTransition(State);
165 reportLeaks(LeakedStreams, C, N);
Anna Zaksd65e55d2012-10-29 22:51:50 +0000166}
167
168void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym,
169 const CallExpr *CallExpr,
170 CheckerContext &C) const {
171 // We reached a bug, stop exploring the path here by generating a sink.
172 ExplodedNode *ErrNode = C.generateSink();
Anna Zaks32133cf2012-10-31 02:32:41 +0000173 // If we've already reached this node on another path, return.
Anna Zaksd65e55d2012-10-29 22:51:50 +0000174 if (!ErrNode)
175 return;
176
Anna Zaksd65e55d2012-10-29 22:51:50 +0000177 // Generate the report.
178 BugReport *R = new BugReport(*DoubleCloseBugType,
179 "Closing a previously closed file stream", ErrNode);
180 R->addRange(CallExpr->getSourceRange());
181 R->markInteresting(FileDescSym);
Jordan Rose785950e2012-11-02 01:53:40 +0000182 C.emitReport(R);
Anna Zaksd65e55d2012-10-29 22:51:50 +0000183}
184
Anna Zaks32133cf2012-10-31 02:32:41 +0000185void SimpleStreamChecker::reportLeaks(SymbolVector LeakedStreams,
186 CheckerContext &C,
187 ExplodedNode *ErrNode) const {
Anna Zaksd65e55d2012-10-29 22:51:50 +0000188 // Attach bug reports to the leak node.
Anna Zaksbdbb17b2012-10-30 04:18:21 +0000189 // TODO: Identify the leaked file descriptor.
Anna Zaksd65e55d2012-10-29 22:51:50 +0000190 for (llvm::SmallVector<SymbolRef, 2>::iterator
191 I = LeakedStreams.begin(), E = LeakedStreams.end(); I != E; ++I) {
192 BugReport *R = new BugReport(*LeakBugType,
193 "Opened file is never closed; potential resource leak", ErrNode);
Anna Zaksbdbb17b2012-10-30 04:18:21 +0000194 R->markInteresting(*I);
Jordan Rose785950e2012-11-02 01:53:40 +0000195 C.emitReport(R);
Anna Zaksd65e55d2012-10-29 22:51:50 +0000196 }
Anna Zaksd65e55d2012-10-29 22:51:50 +0000197}
198
199void SimpleStreamChecker::initIdentifierInfo(ASTContext &Ctx) const {
200 if (IIfopen)
201 return;
202 IIfopen = &Ctx.Idents.get("fopen");
203 IIfclose = &Ctx.Idents.get("fclose");
204}
205
206void ento::registerSimpleStreamChecker(CheckerManager &mgr) {
207 mgr.registerChecker<SimpleStreamChecker>();
208}