1e5dd7070Spatrick //===-- SimpleStreamChecker.cpp -----------------------------------------*- C++ -*--//
2e5dd7070Spatrick //
3e5dd7070Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4e5dd7070Spatrick // See https://llvm.org/LICENSE.txt for license information.
5e5dd7070Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6e5dd7070Spatrick //
7e5dd7070Spatrick //===----------------------------------------------------------------------===//
8e5dd7070Spatrick //
9e5dd7070Spatrick // Defines a checker for proper use of fopen/fclose APIs.
10e5dd7070Spatrick // - If a file has been closed with fclose, it should not be accessed again.
11e5dd7070Spatrick // Accessing a closed file results in undefined behavior.
12e5dd7070Spatrick // - If a file was opened with fopen, it must be closed with fclose before
13e5dd7070Spatrick // the execution ends. Failing to do so results in a resource leak.
14e5dd7070Spatrick //
15e5dd7070Spatrick //===----------------------------------------------------------------------===//
16e5dd7070Spatrick
17e5dd7070Spatrick #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
18e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
19e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/Checker.h"
20*12c85518Srobert #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
21e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
22e5dd7070Spatrick #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
23e5dd7070Spatrick #include <utility>
24e5dd7070Spatrick
25e5dd7070Spatrick using namespace clang;
26e5dd7070Spatrick using namespace ento;
27e5dd7070Spatrick
28e5dd7070Spatrick namespace {
29e5dd7070Spatrick typedef SmallVector<SymbolRef, 2> SymbolVector;
30e5dd7070Spatrick
31e5dd7070Spatrick struct StreamState {
32e5dd7070Spatrick private:
33e5dd7070Spatrick enum Kind { Opened, Closed } K;
StreamState__anonfc40efb00111::StreamState34e5dd7070Spatrick StreamState(Kind InK) : K(InK) { }
35e5dd7070Spatrick
36e5dd7070Spatrick public:
isOpened__anonfc40efb00111::StreamState37e5dd7070Spatrick bool isOpened() const { return K == Opened; }
isClosed__anonfc40efb00111::StreamState38e5dd7070Spatrick bool isClosed() const { return K == Closed; }
39e5dd7070Spatrick
getOpened__anonfc40efb00111::StreamState40e5dd7070Spatrick static StreamState getOpened() { return StreamState(Opened); }
getClosed__anonfc40efb00111::StreamState41e5dd7070Spatrick static StreamState getClosed() { return StreamState(Closed); }
42e5dd7070Spatrick
operator ==__anonfc40efb00111::StreamState43e5dd7070Spatrick bool operator==(const StreamState &X) const {
44e5dd7070Spatrick return K == X.K;
45e5dd7070Spatrick }
Profile__anonfc40efb00111::StreamState46e5dd7070Spatrick void Profile(llvm::FoldingSetNodeID &ID) const {
47e5dd7070Spatrick ID.AddInteger(K);
48e5dd7070Spatrick }
49e5dd7070Spatrick };
50e5dd7070Spatrick
51e5dd7070Spatrick class SimpleStreamChecker : public Checker<check::PostCall,
52e5dd7070Spatrick check::PreCall,
53e5dd7070Spatrick check::DeadSymbols,
54e5dd7070Spatrick check::PointerEscape> {
55e5dd7070Spatrick CallDescription OpenFn, CloseFn;
56e5dd7070Spatrick
57e5dd7070Spatrick std::unique_ptr<BugType> DoubleCloseBugType;
58e5dd7070Spatrick std::unique_ptr<BugType> LeakBugType;
59e5dd7070Spatrick
60e5dd7070Spatrick void reportDoubleClose(SymbolRef FileDescSym,
61e5dd7070Spatrick const CallEvent &Call,
62e5dd7070Spatrick CheckerContext &C) const;
63e5dd7070Spatrick
64e5dd7070Spatrick void reportLeaks(ArrayRef<SymbolRef> LeakedStreams, CheckerContext &C,
65e5dd7070Spatrick ExplodedNode *ErrNode) const;
66e5dd7070Spatrick
67e5dd7070Spatrick bool guaranteedNotToCloseFile(const CallEvent &Call) const;
68e5dd7070Spatrick
69e5dd7070Spatrick public:
70e5dd7070Spatrick SimpleStreamChecker();
71e5dd7070Spatrick
72e5dd7070Spatrick /// Process fopen.
73e5dd7070Spatrick void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
74e5dd7070Spatrick /// Process fclose.
75e5dd7070Spatrick void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
76e5dd7070Spatrick
77e5dd7070Spatrick void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
78e5dd7070Spatrick
79e5dd7070Spatrick /// Stop tracking addresses which escape.
80e5dd7070Spatrick ProgramStateRef checkPointerEscape(ProgramStateRef State,
81e5dd7070Spatrick const InvalidatedSymbols &Escaped,
82e5dd7070Spatrick const CallEvent *Call,
83e5dd7070Spatrick PointerEscapeKind Kind) const;
84e5dd7070Spatrick };
85e5dd7070Spatrick
86e5dd7070Spatrick } // end anonymous namespace
87e5dd7070Spatrick
88e5dd7070Spatrick /// The state of the checker is a map from tracked stream symbols to their
89e5dd7070Spatrick /// state. Let's store it in the ProgramState.
REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap,SymbolRef,StreamState)90e5dd7070Spatrick REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
91e5dd7070Spatrick
92e5dd7070Spatrick SimpleStreamChecker::SimpleStreamChecker()
93*12c85518Srobert : OpenFn({"fopen"}), CloseFn({"fclose"}, 1) {
94e5dd7070Spatrick // Initialize the bug types.
95e5dd7070Spatrick DoubleCloseBugType.reset(
96e5dd7070Spatrick new BugType(this, "Double fclose", "Unix Stream API Error"));
97e5dd7070Spatrick
98e5dd7070Spatrick // Sinks are higher importance bugs as well as calls to assert() or exit(0).
99e5dd7070Spatrick LeakBugType.reset(
100e5dd7070Spatrick new BugType(this, "Resource Leak", "Unix Stream API Error",
101e5dd7070Spatrick /*SuppressOnSink=*/true));
102e5dd7070Spatrick }
103e5dd7070Spatrick
checkPostCall(const CallEvent & Call,CheckerContext & C) const104e5dd7070Spatrick void SimpleStreamChecker::checkPostCall(const CallEvent &Call,
105e5dd7070Spatrick CheckerContext &C) const {
106e5dd7070Spatrick if (!Call.isGlobalCFunction())
107e5dd7070Spatrick return;
108e5dd7070Spatrick
109*12c85518Srobert if (!OpenFn.matches(Call))
110e5dd7070Spatrick return;
111e5dd7070Spatrick
112e5dd7070Spatrick // Get the symbolic value corresponding to the file handle.
113e5dd7070Spatrick SymbolRef FileDesc = Call.getReturnValue().getAsSymbol();
114e5dd7070Spatrick if (!FileDesc)
115e5dd7070Spatrick return;
116e5dd7070Spatrick
117e5dd7070Spatrick // Generate the next transition (an edge in the exploded graph).
118e5dd7070Spatrick ProgramStateRef State = C.getState();
119e5dd7070Spatrick State = State->set<StreamMap>(FileDesc, StreamState::getOpened());
120e5dd7070Spatrick C.addTransition(State);
121e5dd7070Spatrick }
122e5dd7070Spatrick
checkPreCall(const CallEvent & Call,CheckerContext & C) const123e5dd7070Spatrick void SimpleStreamChecker::checkPreCall(const CallEvent &Call,
124e5dd7070Spatrick CheckerContext &C) const {
125e5dd7070Spatrick if (!Call.isGlobalCFunction())
126e5dd7070Spatrick return;
127e5dd7070Spatrick
128*12c85518Srobert if (!CloseFn.matches(Call))
129e5dd7070Spatrick return;
130e5dd7070Spatrick
131e5dd7070Spatrick // Get the symbolic value corresponding to the file handle.
132e5dd7070Spatrick SymbolRef FileDesc = Call.getArgSVal(0).getAsSymbol();
133e5dd7070Spatrick if (!FileDesc)
134e5dd7070Spatrick return;
135e5dd7070Spatrick
136e5dd7070Spatrick // Check if the stream has already been closed.
137e5dd7070Spatrick ProgramStateRef State = C.getState();
138e5dd7070Spatrick const StreamState *SS = State->get<StreamMap>(FileDesc);
139e5dd7070Spatrick if (SS && SS->isClosed()) {
140e5dd7070Spatrick reportDoubleClose(FileDesc, Call, C);
141e5dd7070Spatrick return;
142e5dd7070Spatrick }
143e5dd7070Spatrick
144e5dd7070Spatrick // Generate the next transition, in which the stream is closed.
145e5dd7070Spatrick State = State->set<StreamMap>(FileDesc, StreamState::getClosed());
146e5dd7070Spatrick C.addTransition(State);
147e5dd7070Spatrick }
148e5dd7070Spatrick
isLeaked(SymbolRef Sym,const StreamState & SS,bool IsSymDead,ProgramStateRef State)149e5dd7070Spatrick static bool isLeaked(SymbolRef Sym, const StreamState &SS,
150e5dd7070Spatrick bool IsSymDead, ProgramStateRef State) {
151e5dd7070Spatrick if (IsSymDead && SS.isOpened()) {
152e5dd7070Spatrick // If a symbol is NULL, assume that fopen failed on this path.
153e5dd7070Spatrick // A symbol should only be considered leaked if it is non-null.
154e5dd7070Spatrick ConstraintManager &CMgr = State->getConstraintManager();
155e5dd7070Spatrick ConditionTruthVal OpenFailed = CMgr.isNull(State, Sym);
156e5dd7070Spatrick return !OpenFailed.isConstrainedTrue();
157e5dd7070Spatrick }
158e5dd7070Spatrick return false;
159e5dd7070Spatrick }
160e5dd7070Spatrick
checkDeadSymbols(SymbolReaper & SymReaper,CheckerContext & C) const161e5dd7070Spatrick void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
162e5dd7070Spatrick CheckerContext &C) const {
163e5dd7070Spatrick ProgramStateRef State = C.getState();
164e5dd7070Spatrick SymbolVector LeakedStreams;
165e5dd7070Spatrick StreamMapTy TrackedStreams = State->get<StreamMap>();
166e5dd7070Spatrick for (StreamMapTy::iterator I = TrackedStreams.begin(),
167e5dd7070Spatrick E = TrackedStreams.end(); I != E; ++I) {
168e5dd7070Spatrick SymbolRef Sym = I->first;
169e5dd7070Spatrick bool IsSymDead = SymReaper.isDead(Sym);
170e5dd7070Spatrick
171e5dd7070Spatrick // Collect leaked symbols.
172e5dd7070Spatrick if (isLeaked(Sym, I->second, IsSymDead, State))
173e5dd7070Spatrick LeakedStreams.push_back(Sym);
174e5dd7070Spatrick
175e5dd7070Spatrick // Remove the dead symbol from the streams map.
176e5dd7070Spatrick if (IsSymDead)
177e5dd7070Spatrick State = State->remove<StreamMap>(Sym);
178e5dd7070Spatrick }
179e5dd7070Spatrick
180e5dd7070Spatrick ExplodedNode *N = C.generateNonFatalErrorNode(State);
181e5dd7070Spatrick if (!N)
182e5dd7070Spatrick return;
183e5dd7070Spatrick reportLeaks(LeakedStreams, C, N);
184e5dd7070Spatrick }
185e5dd7070Spatrick
reportDoubleClose(SymbolRef FileDescSym,const CallEvent & Call,CheckerContext & C) const186e5dd7070Spatrick void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym,
187e5dd7070Spatrick const CallEvent &Call,
188e5dd7070Spatrick CheckerContext &C) const {
189e5dd7070Spatrick // We reached a bug, stop exploring the path here by generating a sink.
190e5dd7070Spatrick ExplodedNode *ErrNode = C.generateErrorNode();
191e5dd7070Spatrick // If we've already reached this node on another path, return.
192e5dd7070Spatrick if (!ErrNode)
193e5dd7070Spatrick return;
194e5dd7070Spatrick
195e5dd7070Spatrick // Generate the report.
196e5dd7070Spatrick auto R = std::make_unique<PathSensitiveBugReport>(
197e5dd7070Spatrick *DoubleCloseBugType, "Closing a previously closed file stream", ErrNode);
198e5dd7070Spatrick R->addRange(Call.getSourceRange());
199e5dd7070Spatrick R->markInteresting(FileDescSym);
200e5dd7070Spatrick C.emitReport(std::move(R));
201e5dd7070Spatrick }
202e5dd7070Spatrick
reportLeaks(ArrayRef<SymbolRef> LeakedStreams,CheckerContext & C,ExplodedNode * ErrNode) const203e5dd7070Spatrick void SimpleStreamChecker::reportLeaks(ArrayRef<SymbolRef> LeakedStreams,
204e5dd7070Spatrick CheckerContext &C,
205e5dd7070Spatrick ExplodedNode *ErrNode) const {
206e5dd7070Spatrick // Attach bug reports to the leak node.
207e5dd7070Spatrick // TODO: Identify the leaked file descriptor.
208e5dd7070Spatrick for (SymbolRef LeakedStream : LeakedStreams) {
209e5dd7070Spatrick auto R = std::make_unique<PathSensitiveBugReport>(
210e5dd7070Spatrick *LeakBugType, "Opened file is never closed; potential resource leak",
211e5dd7070Spatrick ErrNode);
212e5dd7070Spatrick R->markInteresting(LeakedStream);
213e5dd7070Spatrick C.emitReport(std::move(R));
214e5dd7070Spatrick }
215e5dd7070Spatrick }
216e5dd7070Spatrick
guaranteedNotToCloseFile(const CallEvent & Call) const217e5dd7070Spatrick bool SimpleStreamChecker::guaranteedNotToCloseFile(const CallEvent &Call) const{
218e5dd7070Spatrick // If it's not in a system header, assume it might close a file.
219e5dd7070Spatrick if (!Call.isInSystemHeader())
220e5dd7070Spatrick return false;
221e5dd7070Spatrick
222e5dd7070Spatrick // Handle cases where we know a buffer's /address/ can escape.
223e5dd7070Spatrick if (Call.argumentsMayEscape())
224e5dd7070Spatrick return false;
225e5dd7070Spatrick
226e5dd7070Spatrick // Note, even though fclose closes the file, we do not list it here
227e5dd7070Spatrick // since the checker is modeling the call.
228e5dd7070Spatrick
229e5dd7070Spatrick return true;
230e5dd7070Spatrick }
231e5dd7070Spatrick
232e5dd7070Spatrick // If the pointer we are tracking escaped, do not track the symbol as
233e5dd7070Spatrick // we cannot reason about it anymore.
234e5dd7070Spatrick ProgramStateRef
checkPointerEscape(ProgramStateRef State,const InvalidatedSymbols & Escaped,const CallEvent * Call,PointerEscapeKind Kind) const235e5dd7070Spatrick SimpleStreamChecker::checkPointerEscape(ProgramStateRef State,
236e5dd7070Spatrick const InvalidatedSymbols &Escaped,
237e5dd7070Spatrick const CallEvent *Call,
238e5dd7070Spatrick PointerEscapeKind Kind) const {
239e5dd7070Spatrick // If we know that the call cannot close a file, there is nothing to do.
240e5dd7070Spatrick if (Kind == PSK_DirectEscapeOnCall && guaranteedNotToCloseFile(*Call)) {
241e5dd7070Spatrick return State;
242e5dd7070Spatrick }
243e5dd7070Spatrick
244e5dd7070Spatrick for (InvalidatedSymbols::const_iterator I = Escaped.begin(),
245e5dd7070Spatrick E = Escaped.end();
246e5dd7070Spatrick I != E; ++I) {
247e5dd7070Spatrick SymbolRef Sym = *I;
248e5dd7070Spatrick
249e5dd7070Spatrick // The symbol escaped. Optimistically, assume that the corresponding file
250e5dd7070Spatrick // handle will be closed somewhere else.
251e5dd7070Spatrick State = State->remove<StreamMap>(Sym);
252e5dd7070Spatrick }
253e5dd7070Spatrick return State;
254e5dd7070Spatrick }
255e5dd7070Spatrick
registerSimpleStreamChecker(CheckerManager & mgr)256e5dd7070Spatrick void ento::registerSimpleStreamChecker(CheckerManager &mgr) {
257e5dd7070Spatrick mgr.registerChecker<SimpleStreamChecker>();
258e5dd7070Spatrick }
259e5dd7070Spatrick
260e5dd7070Spatrick // This checker should be enabled regardless of how language options are set.
shouldRegisterSimpleStreamChecker(const CheckerManager & mgr)261ec727ea7Spatrick bool ento::shouldRegisterSimpleStreamChecker(const CheckerManager &mgr) {
262e5dd7070Spatrick return true;
263e5dd7070Spatrick }
264