clang 24.0.0git
SimpleStreamChecker.cpp
Go to the documentation of this file.
1//===-- SimpleStreamChecker.cpp -----------------------------------*- C++ -*--//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// Defines a checker for proper use of fopen/fclose APIs.
10// - If a file has been closed with fclose, it should not be accessed again.
11// Accessing a closed file results in undefined behavior.
12// - If a file was opened with fopen, it must be closed with fclose before
13// the execution ends. Failing to do so results in a resource leak.
14//
15//===----------------------------------------------------------------------===//
16
23#include <utility>
24
25using namespace clang;
26using namespace ento;
27
28namespace {
29typedef SmallVector<SymbolRef, 2> SymbolVector;
30
31struct StreamState {
32private:
33 enum Kind { Opened, Closed } K;
34 StreamState(Kind InK) : K(InK) { }
35
36public:
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
51class SimpleStreamChecker : public Checker<check::PostCall,
52 check::PreCall,
53 check::DeadSymbols,
54 check::PointerEscape> {
55 const CallDescription OpenFn{CDM::CLibrary, {"fopen"}, 2};
56 const CallDescription CloseFn{CDM::CLibrary, {"fclose"}, 1};
57
58 const BugType DoubleCloseBugType{this, "Double fclose",
59 "Unix Stream API Error"};
60 const BugType LeakBugType{this, "Resource Leak", "Unix Stream API Error",
61 /*SuppressOnSink=*/true};
62
63 void reportDoubleClose(SymbolRef FileDescSym,
64 const CallEvent &Call,
65 CheckerContext &C) const;
66
67 void reportLeaks(ArrayRef<SymbolRef> LeakedStreams, CheckerContext &C,
68 ExplodedNode *ErrNode) const;
69
70public:
71 /// Process fopen.
72 void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
73 /// Process fclose.
74 void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
75
76 void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
77
78 /// Stop tracking addresses which escape.
79 ProgramStateRef checkPointerEscape(ProgramStateRef State,
80 const InvalidatedSymbols &Escaped,
81 const CallEvent *Call,
82 PointerEscapeKind Kind) const;
83};
84
85} // end anonymous namespace
86
87/// The state of the checker is a map from tracked stream symbols to their
88/// state. Let's store it in the ProgramState.
89REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
90
91void SimpleStreamChecker::checkPostCall(const CallEvent &Call,
92 CheckerContext &C) const {
93 if (!OpenFn.matches(Call))
94 return;
95
96 // Get the symbolic value corresponding to the file handle.
97 SymbolRef FileDesc = Call.getReturnValue().getAsSymbol();
98 if (!FileDesc)
99 return;
100
101 // Generate the next transition (an edge in the exploded graph).
102 ProgramStateRef State = C.getState();
103 State = State->set<StreamMap>(FileDesc, StreamState::getOpened());
104 C.addTransition(State);
105}
106
107void SimpleStreamChecker::checkPreCall(const CallEvent &Call,
108 CheckerContext &C) const {
109 if (!CloseFn.matches(Call))
110 return;
111
112 // Get the symbolic value corresponding to the file handle.
113 SymbolRef FileDesc = Call.getArgSVal(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 return;
123 }
124
125 // Generate the next transition, in which the stream is closed.
126 State = State->set<StreamMap>(FileDesc, StreamState::getClosed());
127 C.addTransition(State);
128}
129
130static bool isLeaked(SymbolRef Sym, const StreamState &SS,
131 bool IsSymDead, ProgramStateRef State) {
132 if (IsSymDead && SS.isOpened()) {
133 // If a symbol is NULL, assume that fopen failed on this path.
134 // A symbol should only be considered leaked if it is non-null.
135 ConstraintManager &CMgr = State->getConstraintManager();
136 ConditionTruthVal OpenFailed = CMgr.isNull(State, Sym);
137 return !OpenFailed.isConstrainedTrue();
138 }
139 return false;
140}
141
142void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
143 CheckerContext &C) const {
144 ProgramStateRef State = C.getState();
145 SymbolVector LeakedStreams;
146 StreamMapTy TrackedStreams = State->get<StreamMap>();
147 for (auto [Sym, StreamStatus] : TrackedStreams) {
148 bool IsSymDead = SymReaper.isDead(Sym);
149
150 // Collect leaked symbols.
151 if (isLeaked(Sym, StreamStatus, IsSymDead, State))
152 LeakedStreams.push_back(Sym);
153
154 // Remove the dead symbol from the streams map.
155 if (IsSymDead)
156 State = State->remove<StreamMap>(Sym);
157 }
158
159 ExplodedNode *N = C.generateNonFatalErrorNode(State);
160 if (!N)
161 return;
162 reportLeaks(LeakedStreams, C, N);
163}
164
165void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym,
166 const CallEvent &Call,
167 CheckerContext &C) const {
168 // We reached a bug, stop exploring the path here by generating a sink.
169 ExplodedNode *ErrNode = C.generateErrorNode();
170 // If we've already reached this node on another path, return.
171 if (!ErrNode)
172 return;
173
174 // Generate the report.
175 auto R = std::make_unique<PathSensitiveBugReport>(
176 DoubleCloseBugType, "Closing a previously closed file stream", ErrNode);
177 R->addRange(Call.getSourceRange());
178 R->markInteresting(FileDescSym);
179 C.emitReport(std::move(R));
180}
181
182void SimpleStreamChecker::reportLeaks(ArrayRef<SymbolRef> LeakedStreams,
183 CheckerContext &C,
184 ExplodedNode *ErrNode) const {
185 // Attach bug reports to the leak node.
186 // TODO: Identify the leaked file descriptor.
187 for (SymbolRef LeakedStream : LeakedStreams) {
188 auto R = std::make_unique<PathSensitiveBugReport>(
189 LeakBugType, "Opened file is never closed; potential resource leak",
190 ErrNode);
191 R->markInteresting(LeakedStream);
192 C.emitReport(std::move(R));
193 }
194}
195
196// If the pointer we are tracking escaped, do not track the symbol as
197// we cannot reason about it anymore.
199SimpleStreamChecker::checkPointerEscape(ProgramStateRef State,
200 const InvalidatedSymbols &Escaped,
201 const CallEvent *Call,
202 PointerEscapeKind Kind) const {
203 // If we know that the call cannot close a file, there is nothing to do.
204 if (Kind == PSK_DirectEscapeOnCall && Call->isInSystemHeader()) {
205 return State;
206 }
207
208 for (SymbolRef Sym : Escaped) {
209 // The symbol escaped. Optimistically, assume that the corresponding file
210 // handle will be closed somewhere else.
211 State = State->remove<StreamMap>(Sym);
212 }
213 return State;
214}
215
216void ento::registerSimpleStreamChecker(CheckerManager &mgr) {
217 mgr.registerChecker<SimpleStreamChecker>();
218}
219
220// This checker should be enabled regardless of how language options are set.
221bool ento::shouldRegisterSimpleStreamChecker(const CheckerManager &mgr) {
222 return true;
223}
#define X(type, name)
Definition Value.h:97
#define REGISTER_MAP_WITH_PROGRAMSTATE(Name, Key, Value)
Declares an immutable map of type NameTy, suitable for placement into the ProgramState.
static bool isLeaked(SymbolRef Sym, const StreamState &SS, bool IsSymDead, ProgramStateRef State)
bool matches(const CallEvent &Call) const
Returns true if the CallEvent is a call to a function that matches the CallDescription.
Represents an abstract call to a function or method along a particular path.
Definition CallEvent.h:152
CHECKER * registerChecker(AT &&...Args)
Register a single-part checker (derived from Checker): construct its singleton instance,...
Simple checker classes that implement one frontend (i.e.
Definition Checker.h:565
bool isConstrainedTrue() const
Return true if the constraint is perfectly constrained to 'true'.
ConditionTruthVal isNull(ProgramStateRef State, SymbolRef Sym)
Convenience method to query the state to see if a symbol is null or not null, or if neither assumptio...
bool isDead(SymbolRef sym)
Returns whether or not a symbol has been confirmed dead.
PointerEscapeKind
Describes the different reasons a pointer escapes during analysis.
@ PSK_DirectEscapeOnCall
The pointer has been passed to a function call directly.
llvm::DenseSet< SymbolRef > InvalidatedSymbols
Definition Store.h:50
IntrusiveRefCntPtr< const ProgramState > ProgramStateRef
const SymExpr * SymbolRef
Definition SymExpr.h:133
Top level wrappers for InstallAPI frontend operations.
bool operator==(const CallGraphNode::CallRecord &LHS, const CallGraphNode::CallRecord &RHS)
Definition CallGraph.h:218