xref: /freebsd/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp (revision 5ffd83dbcc34f10e07f6d3e968ae6365869615f4)
10b57cec5SDimitry Andric //===-- SimpleStreamChecker.cpp -----------------------------------------*- C++ -*--//
20b57cec5SDimitry Andric //
30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
60b57cec5SDimitry Andric //
70b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
80b57cec5SDimitry Andric //
90b57cec5SDimitry Andric // Defines a checker for proper use of fopen/fclose APIs.
100b57cec5SDimitry Andric //   - If a file has been closed with fclose, it should not be accessed again.
110b57cec5SDimitry Andric //   Accessing a closed file results in undefined behavior.
120b57cec5SDimitry Andric //   - If a file was opened with fopen, it must be closed with fclose before
130b57cec5SDimitry Andric //   the execution ends. Failing to do so results in a resource leak.
140b57cec5SDimitry Andric //
150b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
160b57cec5SDimitry Andric 
170b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
180b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
190b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/Checker.h"
200b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
210b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
220b57cec5SDimitry Andric #include <utility>
230b57cec5SDimitry Andric 
240b57cec5SDimitry Andric using namespace clang;
250b57cec5SDimitry Andric using namespace ento;
260b57cec5SDimitry Andric 
270b57cec5SDimitry Andric namespace {
280b57cec5SDimitry Andric typedef SmallVector<SymbolRef, 2> SymbolVector;
290b57cec5SDimitry Andric 
300b57cec5SDimitry Andric struct StreamState {
310b57cec5SDimitry Andric private:
320b57cec5SDimitry Andric   enum Kind { Opened, Closed } K;
330b57cec5SDimitry Andric   StreamState(Kind InK) : K(InK) { }
340b57cec5SDimitry Andric 
350b57cec5SDimitry Andric public:
360b57cec5SDimitry Andric   bool isOpened() const { return K == Opened; }
370b57cec5SDimitry Andric   bool isClosed() const { return K == Closed; }
380b57cec5SDimitry Andric 
390b57cec5SDimitry Andric   static StreamState getOpened() { return StreamState(Opened); }
400b57cec5SDimitry Andric   static StreamState getClosed() { return StreamState(Closed); }
410b57cec5SDimitry Andric 
420b57cec5SDimitry Andric   bool operator==(const StreamState &X) const {
430b57cec5SDimitry Andric     return K == X.K;
440b57cec5SDimitry Andric   }
450b57cec5SDimitry Andric   void Profile(llvm::FoldingSetNodeID &ID) const {
460b57cec5SDimitry Andric     ID.AddInteger(K);
470b57cec5SDimitry Andric   }
480b57cec5SDimitry Andric };
490b57cec5SDimitry Andric 
500b57cec5SDimitry Andric class SimpleStreamChecker : public Checker<check::PostCall,
510b57cec5SDimitry Andric                                            check::PreCall,
520b57cec5SDimitry Andric                                            check::DeadSymbols,
530b57cec5SDimitry Andric                                            check::PointerEscape> {
540b57cec5SDimitry Andric   CallDescription OpenFn, CloseFn;
550b57cec5SDimitry Andric 
560b57cec5SDimitry Andric   std::unique_ptr<BugType> DoubleCloseBugType;
570b57cec5SDimitry Andric   std::unique_ptr<BugType> LeakBugType;
580b57cec5SDimitry Andric 
590b57cec5SDimitry Andric   void reportDoubleClose(SymbolRef FileDescSym,
600b57cec5SDimitry Andric                          const CallEvent &Call,
610b57cec5SDimitry Andric                          CheckerContext &C) const;
620b57cec5SDimitry Andric 
630b57cec5SDimitry Andric   void reportLeaks(ArrayRef<SymbolRef> LeakedStreams, CheckerContext &C,
640b57cec5SDimitry Andric                    ExplodedNode *ErrNode) const;
650b57cec5SDimitry Andric 
660b57cec5SDimitry Andric   bool guaranteedNotToCloseFile(const CallEvent &Call) const;
670b57cec5SDimitry Andric 
680b57cec5SDimitry Andric public:
690b57cec5SDimitry Andric   SimpleStreamChecker();
700b57cec5SDimitry Andric 
710b57cec5SDimitry Andric   /// Process fopen.
720b57cec5SDimitry Andric   void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
730b57cec5SDimitry Andric   /// Process fclose.
740b57cec5SDimitry Andric   void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
750b57cec5SDimitry Andric 
760b57cec5SDimitry Andric   void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
770b57cec5SDimitry Andric 
780b57cec5SDimitry Andric   /// Stop tracking addresses which escape.
790b57cec5SDimitry Andric   ProgramStateRef checkPointerEscape(ProgramStateRef State,
800b57cec5SDimitry Andric                                     const InvalidatedSymbols &Escaped,
810b57cec5SDimitry Andric                                     const CallEvent *Call,
820b57cec5SDimitry Andric                                     PointerEscapeKind Kind) const;
830b57cec5SDimitry Andric };
840b57cec5SDimitry Andric 
850b57cec5SDimitry Andric } // end anonymous namespace
860b57cec5SDimitry Andric 
870b57cec5SDimitry Andric /// The state of the checker is a map from tracked stream symbols to their
880b57cec5SDimitry Andric /// state. Let's store it in the ProgramState.
890b57cec5SDimitry Andric REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
900b57cec5SDimitry Andric 
910b57cec5SDimitry Andric namespace {
920b57cec5SDimitry Andric class StopTrackingCallback final : public SymbolVisitor {
930b57cec5SDimitry Andric   ProgramStateRef state;
940b57cec5SDimitry Andric public:
950b57cec5SDimitry Andric   StopTrackingCallback(ProgramStateRef st) : state(std::move(st)) {}
960b57cec5SDimitry Andric   ProgramStateRef getState() const { return state; }
970b57cec5SDimitry Andric 
980b57cec5SDimitry Andric   bool VisitSymbol(SymbolRef sym) override {
990b57cec5SDimitry Andric     state = state->remove<StreamMap>(sym);
1000b57cec5SDimitry Andric     return true;
1010b57cec5SDimitry Andric   }
1020b57cec5SDimitry Andric };
1030b57cec5SDimitry Andric } // end anonymous namespace
1040b57cec5SDimitry Andric 
1050b57cec5SDimitry Andric SimpleStreamChecker::SimpleStreamChecker()
1060b57cec5SDimitry Andric     : OpenFn("fopen"), CloseFn("fclose", 1) {
1070b57cec5SDimitry Andric   // Initialize the bug types.
1080b57cec5SDimitry Andric   DoubleCloseBugType.reset(
1090b57cec5SDimitry Andric       new BugType(this, "Double fclose", "Unix Stream API Error"));
1100b57cec5SDimitry Andric 
1110b57cec5SDimitry Andric   // Sinks are higher importance bugs as well as calls to assert() or exit(0).
1120b57cec5SDimitry Andric   LeakBugType.reset(
1130b57cec5SDimitry Andric       new BugType(this, "Resource Leak", "Unix Stream API Error",
1140b57cec5SDimitry Andric                   /*SuppressOnSink=*/true));
1150b57cec5SDimitry Andric }
1160b57cec5SDimitry Andric 
1170b57cec5SDimitry Andric void SimpleStreamChecker::checkPostCall(const CallEvent &Call,
1180b57cec5SDimitry Andric                                         CheckerContext &C) const {
1190b57cec5SDimitry Andric   if (!Call.isGlobalCFunction())
1200b57cec5SDimitry Andric     return;
1210b57cec5SDimitry Andric 
1220b57cec5SDimitry Andric   if (!Call.isCalled(OpenFn))
1230b57cec5SDimitry Andric     return;
1240b57cec5SDimitry Andric 
1250b57cec5SDimitry Andric   // Get the symbolic value corresponding to the file handle.
1260b57cec5SDimitry Andric   SymbolRef FileDesc = Call.getReturnValue().getAsSymbol();
1270b57cec5SDimitry Andric   if (!FileDesc)
1280b57cec5SDimitry Andric     return;
1290b57cec5SDimitry Andric 
1300b57cec5SDimitry Andric   // Generate the next transition (an edge in the exploded graph).
1310b57cec5SDimitry Andric   ProgramStateRef State = C.getState();
1320b57cec5SDimitry Andric   State = State->set<StreamMap>(FileDesc, StreamState::getOpened());
1330b57cec5SDimitry Andric   C.addTransition(State);
1340b57cec5SDimitry Andric }
1350b57cec5SDimitry Andric 
1360b57cec5SDimitry Andric void SimpleStreamChecker::checkPreCall(const CallEvent &Call,
1370b57cec5SDimitry Andric                                        CheckerContext &C) const {
1380b57cec5SDimitry Andric   if (!Call.isGlobalCFunction())
1390b57cec5SDimitry Andric     return;
1400b57cec5SDimitry Andric 
1410b57cec5SDimitry Andric   if (!Call.isCalled(CloseFn))
1420b57cec5SDimitry Andric     return;
1430b57cec5SDimitry Andric 
1440b57cec5SDimitry Andric   // Get the symbolic value corresponding to the file handle.
1450b57cec5SDimitry Andric   SymbolRef FileDesc = Call.getArgSVal(0).getAsSymbol();
1460b57cec5SDimitry Andric   if (!FileDesc)
1470b57cec5SDimitry Andric     return;
1480b57cec5SDimitry Andric 
1490b57cec5SDimitry Andric   // Check if the stream has already been closed.
1500b57cec5SDimitry Andric   ProgramStateRef State = C.getState();
1510b57cec5SDimitry Andric   const StreamState *SS = State->get<StreamMap>(FileDesc);
1520b57cec5SDimitry Andric   if (SS && SS->isClosed()) {
1530b57cec5SDimitry Andric     reportDoubleClose(FileDesc, Call, C);
1540b57cec5SDimitry Andric     return;
1550b57cec5SDimitry Andric   }
1560b57cec5SDimitry Andric 
1570b57cec5SDimitry Andric   // Generate the next transition, in which the stream is closed.
1580b57cec5SDimitry Andric   State = State->set<StreamMap>(FileDesc, StreamState::getClosed());
1590b57cec5SDimitry Andric   C.addTransition(State);
1600b57cec5SDimitry Andric }
1610b57cec5SDimitry Andric 
1620b57cec5SDimitry Andric static bool isLeaked(SymbolRef Sym, const StreamState &SS,
1630b57cec5SDimitry Andric                      bool IsSymDead, ProgramStateRef State) {
1640b57cec5SDimitry Andric   if (IsSymDead && SS.isOpened()) {
1650b57cec5SDimitry Andric     // If a symbol is NULL, assume that fopen failed on this path.
1660b57cec5SDimitry Andric     // A symbol should only be considered leaked if it is non-null.
1670b57cec5SDimitry Andric     ConstraintManager &CMgr = State->getConstraintManager();
1680b57cec5SDimitry Andric     ConditionTruthVal OpenFailed = CMgr.isNull(State, Sym);
1690b57cec5SDimitry Andric     return !OpenFailed.isConstrainedTrue();
1700b57cec5SDimitry Andric   }
1710b57cec5SDimitry Andric   return false;
1720b57cec5SDimitry Andric }
1730b57cec5SDimitry Andric 
1740b57cec5SDimitry Andric void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
1750b57cec5SDimitry Andric                                            CheckerContext &C) const {
1760b57cec5SDimitry Andric   ProgramStateRef State = C.getState();
1770b57cec5SDimitry Andric   SymbolVector LeakedStreams;
1780b57cec5SDimitry Andric   StreamMapTy TrackedStreams = State->get<StreamMap>();
1790b57cec5SDimitry Andric   for (StreamMapTy::iterator I = TrackedStreams.begin(),
1800b57cec5SDimitry Andric                              E = TrackedStreams.end(); I != E; ++I) {
1810b57cec5SDimitry Andric     SymbolRef Sym = I->first;
1820b57cec5SDimitry Andric     bool IsSymDead = SymReaper.isDead(Sym);
1830b57cec5SDimitry Andric 
1840b57cec5SDimitry Andric     // Collect leaked symbols.
1850b57cec5SDimitry Andric     if (isLeaked(Sym, I->second, IsSymDead, State))
1860b57cec5SDimitry Andric       LeakedStreams.push_back(Sym);
1870b57cec5SDimitry Andric 
1880b57cec5SDimitry Andric     // Remove the dead symbol from the streams map.
1890b57cec5SDimitry Andric     if (IsSymDead)
1900b57cec5SDimitry Andric       State = State->remove<StreamMap>(Sym);
1910b57cec5SDimitry Andric   }
1920b57cec5SDimitry Andric 
1930b57cec5SDimitry Andric   ExplodedNode *N = C.generateNonFatalErrorNode(State);
1940b57cec5SDimitry Andric   if (!N)
1950b57cec5SDimitry Andric     return;
1960b57cec5SDimitry Andric   reportLeaks(LeakedStreams, C, N);
1970b57cec5SDimitry Andric }
1980b57cec5SDimitry Andric 
1990b57cec5SDimitry Andric void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym,
2000b57cec5SDimitry Andric                                             const CallEvent &Call,
2010b57cec5SDimitry Andric                                             CheckerContext &C) const {
2020b57cec5SDimitry Andric   // We reached a bug, stop exploring the path here by generating a sink.
2030b57cec5SDimitry Andric   ExplodedNode *ErrNode = C.generateErrorNode();
2040b57cec5SDimitry Andric   // If we've already reached this node on another path, return.
2050b57cec5SDimitry Andric   if (!ErrNode)
2060b57cec5SDimitry Andric     return;
2070b57cec5SDimitry Andric 
2080b57cec5SDimitry Andric   // Generate the report.
209a7dea167SDimitry Andric   auto R = std::make_unique<PathSensitiveBugReport>(
210a7dea167SDimitry Andric       *DoubleCloseBugType, "Closing a previously closed file stream", ErrNode);
2110b57cec5SDimitry Andric   R->addRange(Call.getSourceRange());
2120b57cec5SDimitry Andric   R->markInteresting(FileDescSym);
2130b57cec5SDimitry Andric   C.emitReport(std::move(R));
2140b57cec5SDimitry Andric }
2150b57cec5SDimitry Andric 
2160b57cec5SDimitry Andric void SimpleStreamChecker::reportLeaks(ArrayRef<SymbolRef> LeakedStreams,
2170b57cec5SDimitry Andric                                       CheckerContext &C,
2180b57cec5SDimitry Andric                                       ExplodedNode *ErrNode) const {
2190b57cec5SDimitry Andric   // Attach bug reports to the leak node.
2200b57cec5SDimitry Andric   // TODO: Identify the leaked file descriptor.
2210b57cec5SDimitry Andric   for (SymbolRef LeakedStream : LeakedStreams) {
222a7dea167SDimitry Andric     auto R = std::make_unique<PathSensitiveBugReport>(
223a7dea167SDimitry Andric         *LeakBugType, "Opened file is never closed; potential resource leak",
224a7dea167SDimitry Andric         ErrNode);
2250b57cec5SDimitry Andric     R->markInteresting(LeakedStream);
2260b57cec5SDimitry Andric     C.emitReport(std::move(R));
2270b57cec5SDimitry Andric   }
2280b57cec5SDimitry Andric }
2290b57cec5SDimitry Andric 
2300b57cec5SDimitry Andric bool SimpleStreamChecker::guaranteedNotToCloseFile(const CallEvent &Call) const{
2310b57cec5SDimitry Andric   // If it's not in a system header, assume it might close a file.
2320b57cec5SDimitry Andric   if (!Call.isInSystemHeader())
2330b57cec5SDimitry Andric     return false;
2340b57cec5SDimitry Andric 
2350b57cec5SDimitry Andric   // Handle cases where we know a buffer's /address/ can escape.
2360b57cec5SDimitry Andric   if (Call.argumentsMayEscape())
2370b57cec5SDimitry Andric     return false;
2380b57cec5SDimitry Andric 
2390b57cec5SDimitry Andric   // Note, even though fclose closes the file, we do not list it here
2400b57cec5SDimitry Andric   // since the checker is modeling the call.
2410b57cec5SDimitry Andric 
2420b57cec5SDimitry Andric   return true;
2430b57cec5SDimitry Andric }
2440b57cec5SDimitry Andric 
2450b57cec5SDimitry Andric // If the pointer we are tracking escaped, do not track the symbol as
2460b57cec5SDimitry Andric // we cannot reason about it anymore.
2470b57cec5SDimitry Andric ProgramStateRef
2480b57cec5SDimitry Andric SimpleStreamChecker::checkPointerEscape(ProgramStateRef State,
2490b57cec5SDimitry Andric                                         const InvalidatedSymbols &Escaped,
2500b57cec5SDimitry Andric                                         const CallEvent *Call,
2510b57cec5SDimitry Andric                                         PointerEscapeKind Kind) const {
2520b57cec5SDimitry Andric   // If we know that the call cannot close a file, there is nothing to do.
2530b57cec5SDimitry Andric   if (Kind == PSK_DirectEscapeOnCall && guaranteedNotToCloseFile(*Call)) {
2540b57cec5SDimitry Andric     return State;
2550b57cec5SDimitry Andric   }
2560b57cec5SDimitry Andric 
2570b57cec5SDimitry Andric   for (InvalidatedSymbols::const_iterator I = Escaped.begin(),
2580b57cec5SDimitry Andric                                           E = Escaped.end();
2590b57cec5SDimitry Andric                                           I != E; ++I) {
2600b57cec5SDimitry Andric     SymbolRef Sym = *I;
2610b57cec5SDimitry Andric 
2620b57cec5SDimitry Andric     // The symbol escaped. Optimistically, assume that the corresponding file
2630b57cec5SDimitry Andric     // handle will be closed somewhere else.
2640b57cec5SDimitry Andric     State = State->remove<StreamMap>(Sym);
2650b57cec5SDimitry Andric   }
2660b57cec5SDimitry Andric   return State;
2670b57cec5SDimitry Andric }
2680b57cec5SDimitry Andric 
2690b57cec5SDimitry Andric void ento::registerSimpleStreamChecker(CheckerManager &mgr) {
2700b57cec5SDimitry Andric   mgr.registerChecker<SimpleStreamChecker>();
2710b57cec5SDimitry Andric }
2720b57cec5SDimitry Andric 
2730b57cec5SDimitry Andric // This checker should be enabled regardless of how language options are set.
274*5ffd83dbSDimitry Andric bool ento::shouldRegisterSimpleStreamChecker(const CheckerManager &mgr) {
2750b57cec5SDimitry Andric   return true;
2760b57cec5SDimitry Andric }
277