Coverage Report

Created: 2023-09-30 09:22

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