1 //===-- StreamChecker.cpp -----------------------------------------*- C++ -*--//
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
7 //===----------------------------------------------------------------------===//
9 // This file defines checkers that model and check stream handling functions.
11 //===----------------------------------------------------------------------===//
13 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
14 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
15 #include "clang/StaticAnalyzer/Core/Checker.h"
16 #include "clang/StaticAnalyzer/Core/CheckerManager.h"
17 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
18 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
19 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
20 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
21 #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
24 using namespace clang;
26 using namespace std::placeholders;
31 enum Kind { Opened, Closed, OpenFailed, Escaped } K;
33 StreamState(Kind k) : K(k) {}
35 bool isOpened() const { return K == Opened; }
36 bool isClosed() const { return K == Closed; }
37 //bool isOpenFailed() const { return K == OpenFailed; }
38 //bool isEscaped() const { return K == Escaped; }
40 bool operator==(const StreamState &X) const { return K == X.K; }
42 static StreamState getOpened() { return StreamState(Opened); }
43 static StreamState getClosed() { return StreamState(Closed); }
44 static StreamState getOpenFailed() { return StreamState(OpenFailed); }
45 static StreamState getEscaped() { return StreamState(Escaped); }
47 void Profile(llvm::FoldingSetNodeID &ID) const {
52 class StreamChecker : public Checker<eval::Call,
53 check::DeadSymbols > {
54 mutable std::unique_ptr<BuiltinBug> BT_nullfp, BT_illegalwhence,
55 BT_doubleclose, BT_ResourceLeak;
58 bool evalCall(const CallEvent &Call, CheckerContext &C) const;
59 void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
62 using FnCheck = std::function<void(const StreamChecker *, const CallEvent &,
65 CallDescriptionMap<FnCheck> Callbacks = {
66 {{"fopen"}, &StreamChecker::evalFopen},
67 {{"freopen", 3}, &StreamChecker::evalFreopen},
68 {{"tmpfile"}, &StreamChecker::evalFopen},
69 {{"fclose", 1}, &StreamChecker::evalFclose},
71 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 3)},
73 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 3)},
74 {{"fseek", 3}, &StreamChecker::evalFseek},
76 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)},
78 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)},
80 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)},
82 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)},
84 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)},
86 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)},
88 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)},
90 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)},
93 void evalFopen(const CallEvent &Call, CheckerContext &C) const;
94 void evalFreopen(const CallEvent &Call, CheckerContext &C) const;
95 void evalFclose(const CallEvent &Call, CheckerContext &C) const;
96 void evalFseek(const CallEvent &Call, CheckerContext &C) const;
98 void checkArgNullStream(const CallEvent &Call, CheckerContext &C,
100 bool checkNullStream(SVal SV, CheckerContext &C,
101 ProgramStateRef &State) const;
102 void checkFseekWhence(SVal SV, CheckerContext &C,
103 ProgramStateRef &State) const;
104 bool checkDoubleClose(const CallEvent &Call, CheckerContext &C,
105 ProgramStateRef &State) const;
108 } // end anonymous namespace
110 REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
113 bool StreamChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
114 const auto *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
115 if (!FD || FD->getKind() != Decl::Function)
118 // Recognize "global C functions" with only integral or pointer arguments
119 // (and matching name) as stream functions.
120 if (!Call.isGlobalCFunction())
122 for (auto P : Call.parameters()) {
123 QualType T = P->getType();
124 if (!T->isIntegralOrEnumerationType() && !T->isPointerType())
128 const FnCheck *Callback = Callbacks.lookup(Call);
132 (*Callback)(this, Call, C);
134 return C.isDifferent();
137 void StreamChecker::evalFopen(const CallEvent &Call, CheckerContext &C) const {
138 ProgramStateRef state = C.getState();
139 SValBuilder &svalBuilder = C.getSValBuilder();
140 const LocationContext *LCtx = C.getPredecessor()->getLocationContext();
141 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
146 svalBuilder.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount())
147 .castAs<DefinedSVal>();
148 state = state->BindExpr(CE, C.getLocationContext(), RetVal);
150 ConstraintManager &CM = C.getConstraintManager();
151 // Bifurcate the state into two: one with a valid FILE* pointer, the other
153 ProgramStateRef stateNotNull, stateNull;
154 std::tie(stateNotNull, stateNull) = CM.assumeDual(state, RetVal);
156 SymbolRef Sym = RetVal.getAsSymbol();
157 assert(Sym && "RetVal must be a symbol here.");
158 stateNotNull = stateNotNull->set<StreamMap>(Sym, StreamState::getOpened());
159 stateNull = stateNull->set<StreamMap>(Sym, StreamState::getOpenFailed());
161 C.addTransition(stateNotNull);
162 C.addTransition(stateNull);
165 void StreamChecker::evalFreopen(const CallEvent &Call,
166 CheckerContext &C) const {
167 ProgramStateRef State = C.getState();
169 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
173 Optional<DefinedSVal> StreamVal = Call.getArgSVal(2).getAs<DefinedSVal>();
176 // Do not allow NULL as passed stream pointer.
177 // This is not specified in the man page but may crash on some system.
178 checkNullStream(*StreamVal, C, State);
179 // Check if error was generated.
183 SymbolRef StreamSym = StreamVal->getAsSymbol();
184 // Do not care about special values for stream ("(FILE *)0x12345"?).
188 // Generate state for non-failed case.
189 // Return value is the passed stream pointer.
190 // According to the documentations, the stream is closed first
191 // but any close error is ignored. The state changes to (or remains) opened.
192 ProgramStateRef StateRetNotNull =
193 State->BindExpr(CE, C.getLocationContext(), *StreamVal);
194 // Generate state for NULL return value.
195 // Stream switches to OpenFailed state.
196 ProgramStateRef StateRetNull = State->BindExpr(CE, C.getLocationContext(),
197 C.getSValBuilder().makeNull());
200 StateRetNotNull->set<StreamMap>(StreamSym, StreamState::getOpened());
202 StateRetNull->set<StreamMap>(StreamSym, StreamState::getOpenFailed());
204 C.addTransition(StateRetNotNull);
205 C.addTransition(StateRetNull);
208 void StreamChecker::evalFclose(const CallEvent &Call, CheckerContext &C) const {
209 ProgramStateRef State = C.getState();
210 if (checkDoubleClose(Call, C, State))
211 C.addTransition(State);
214 void StreamChecker::evalFseek(const CallEvent &Call, CheckerContext &C) const {
215 const Expr *AE2 = Call.getArgExpr(2);
219 ProgramStateRef State = C.getState();
221 bool StateChanged = checkNullStream(Call.getArgSVal(0), C, State);
222 // Check if error was generated.
226 // Check the legality of the 'whence' argument of 'fseek'.
227 checkFseekWhence(State->getSVal(AE2, C.getLocationContext()), C, State);
229 if (!C.isDifferent() && StateChanged)
230 C.addTransition(State);
235 void StreamChecker::checkArgNullStream(const CallEvent &Call, CheckerContext &C,
236 unsigned ArgI) const {
237 ProgramStateRef State = C.getState();
238 if (checkNullStream(Call.getArgSVal(ArgI), C, State))
239 C.addTransition(State);
242 bool StreamChecker::checkNullStream(SVal SV, CheckerContext &C,
243 ProgramStateRef &State) const {
244 Optional<DefinedSVal> DV = SV.getAs<DefinedSVal>();
248 ConstraintManager &CM = C.getConstraintManager();
249 ProgramStateRef StateNotNull, StateNull;
250 std::tie(StateNotNull, StateNull) = CM.assumeDual(C.getState(), *DV);
252 if (!StateNotNull && StateNull) {
253 if (ExplodedNode *N = C.generateErrorNode(StateNull)) {
255 BT_nullfp.reset(new BuiltinBug(this, "NULL stream pointer",
256 "Stream pointer might be NULL."));
257 C.emitReport(std::make_unique<PathSensitiveBugReport>(
258 *BT_nullfp, BT_nullfp->getDescription(), N));
264 State = StateNotNull;
271 void StreamChecker::checkFseekWhence(SVal SV, CheckerContext &C,
272 ProgramStateRef &State) const {
273 Optional<nonloc::ConcreteInt> CI = SV.getAs<nonloc::ConcreteInt>();
277 int64_t X = CI->getValue().getSExtValue();
278 if (X >= 0 && X <= 2)
281 if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) {
282 if (!BT_illegalwhence)
283 BT_illegalwhence.reset(
284 new BuiltinBug(this, "Illegal whence argument",
285 "The whence argument to fseek() should be "
286 "SEEK_SET, SEEK_END, or SEEK_CUR."));
287 C.emitReport(std::make_unique<PathSensitiveBugReport>(
288 *BT_illegalwhence, BT_illegalwhence->getDescription(), N));
292 bool StreamChecker::checkDoubleClose(const CallEvent &Call, CheckerContext &C,
293 ProgramStateRef &State) const {
294 SymbolRef Sym = Call.getArgSVal(0).getAsSymbol();
298 const StreamState *SS = State->get<StreamMap>(Sym);
300 // If the file stream is not tracked, return.
304 // Check: Double close a File Descriptor could cause undefined behaviour.
305 // Conforming to man-pages
306 if (SS->isClosed()) {
307 ExplodedNode *N = C.generateErrorNode();
310 BT_doubleclose.reset(new BuiltinBug(
311 this, "Double fclose", "Try to close a file Descriptor already"
312 " closed. Cause undefined behaviour."));
313 C.emitReport(std::make_unique<PathSensitiveBugReport>(
314 *BT_doubleclose, BT_doubleclose->getDescription(), N));
319 // Close the File Descriptor.
320 State = State->set<StreamMap>(Sym, StreamState::getClosed());
325 void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
326 CheckerContext &C) const {
327 ProgramStateRef State = C.getState();
329 // TODO: Clean up the state.
330 const StreamMapTy &Map = State->get<StreamMap>();
331 for (const auto &I: Map) {
332 SymbolRef Sym = I.first;
333 const StreamState &SS = I.second;
334 if (!SymReaper.isDead(Sym) || !SS.isOpened())
337 ExplodedNode *N = C.generateErrorNode();
341 if (!BT_ResourceLeak)
342 BT_ResourceLeak.reset(
343 new BuiltinBug(this, "Resource Leak",
344 "Opened File never closed. Potential Resource leak."));
345 C.emitReport(std::make_unique<PathSensitiveBugReport>(
346 *BT_ResourceLeak, BT_ResourceLeak->getDescription(), N));
350 void ento::registerStreamChecker(CheckerManager &mgr) {
351 mgr.registerChecker<StreamChecker>();
354 bool ento::shouldRegisterStreamChecker(const LangOptions &LO) {