xref: /freebsd/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp (revision 06c3fb2749bda94cb5201f81ffdb8fa6c3161b2e)
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"
20349cc55cSDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
210b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
220b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
230b57cec5SDimitry Andric #include <utility>
240b57cec5SDimitry Andric 
250b57cec5SDimitry Andric using namespace clang;
260b57cec5SDimitry Andric using namespace ento;
270b57cec5SDimitry Andric 
280b57cec5SDimitry Andric namespace {
290b57cec5SDimitry Andric typedef SmallVector<SymbolRef, 2> SymbolVector;
300b57cec5SDimitry Andric 
310b57cec5SDimitry Andric struct StreamState {
320b57cec5SDimitry Andric private:
330b57cec5SDimitry Andric   enum Kind { Opened, Closed } K;
340b57cec5SDimitry Andric   StreamState(Kind InK) : K(InK) { }
350b57cec5SDimitry Andric 
360b57cec5SDimitry Andric public:
370b57cec5SDimitry Andric   bool isOpened() const { return K == Opened; }
380b57cec5SDimitry Andric   bool isClosed() const { return K == Closed; }
390b57cec5SDimitry Andric 
400b57cec5SDimitry Andric   static StreamState getOpened() { return StreamState(Opened); }
410b57cec5SDimitry Andric   static StreamState getClosed() { return StreamState(Closed); }
420b57cec5SDimitry Andric 
430b57cec5SDimitry Andric   bool operator==(const StreamState &X) const {
440b57cec5SDimitry Andric     return K == X.K;
450b57cec5SDimitry Andric   }
460b57cec5SDimitry Andric   void Profile(llvm::FoldingSetNodeID &ID) const {
470b57cec5SDimitry Andric     ID.AddInteger(K);
480b57cec5SDimitry Andric   }
490b57cec5SDimitry Andric };
500b57cec5SDimitry Andric 
510b57cec5SDimitry Andric class SimpleStreamChecker : public Checker<check::PostCall,
520b57cec5SDimitry Andric                                            check::PreCall,
530b57cec5SDimitry Andric                                            check::DeadSymbols,
540b57cec5SDimitry Andric                                            check::PointerEscape> {
550b57cec5SDimitry Andric   CallDescription OpenFn, CloseFn;
560b57cec5SDimitry Andric 
570b57cec5SDimitry Andric   std::unique_ptr<BugType> DoubleCloseBugType;
580b57cec5SDimitry Andric   std::unique_ptr<BugType> LeakBugType;
590b57cec5SDimitry Andric 
600b57cec5SDimitry Andric   void reportDoubleClose(SymbolRef FileDescSym,
610b57cec5SDimitry Andric                          const CallEvent &Call,
620b57cec5SDimitry Andric                          CheckerContext &C) const;
630b57cec5SDimitry Andric 
640b57cec5SDimitry Andric   void reportLeaks(ArrayRef<SymbolRef> LeakedStreams, CheckerContext &C,
650b57cec5SDimitry Andric                    ExplodedNode *ErrNode) const;
660b57cec5SDimitry Andric 
670b57cec5SDimitry Andric   bool guaranteedNotToCloseFile(const CallEvent &Call) const;
680b57cec5SDimitry Andric 
690b57cec5SDimitry Andric public:
700b57cec5SDimitry Andric   SimpleStreamChecker();
710b57cec5SDimitry Andric 
720b57cec5SDimitry Andric   /// Process fopen.
730b57cec5SDimitry Andric   void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
740b57cec5SDimitry Andric   /// Process fclose.
750b57cec5SDimitry Andric   void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
760b57cec5SDimitry Andric 
770b57cec5SDimitry Andric   void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
780b57cec5SDimitry Andric 
790b57cec5SDimitry Andric   /// Stop tracking addresses which escape.
800b57cec5SDimitry Andric   ProgramStateRef checkPointerEscape(ProgramStateRef State,
810b57cec5SDimitry Andric                                     const InvalidatedSymbols &Escaped,
820b57cec5SDimitry Andric                                     const CallEvent *Call,
830b57cec5SDimitry Andric                                     PointerEscapeKind Kind) const;
840b57cec5SDimitry Andric };
850b57cec5SDimitry Andric 
860b57cec5SDimitry Andric } // end anonymous namespace
870b57cec5SDimitry Andric 
880b57cec5SDimitry Andric /// The state of the checker is a map from tracked stream symbols to their
890b57cec5SDimitry Andric /// state. Let's store it in the ProgramState.
900b57cec5SDimitry Andric REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
910b57cec5SDimitry Andric 
920b57cec5SDimitry Andric SimpleStreamChecker::SimpleStreamChecker()
93bdd1243dSDimitry Andric     : OpenFn({"fopen"}), CloseFn({"fclose"}, 1) {
940b57cec5SDimitry Andric   // Initialize the bug types.
950b57cec5SDimitry Andric   DoubleCloseBugType.reset(
960b57cec5SDimitry Andric       new BugType(this, "Double fclose", "Unix Stream API Error"));
970b57cec5SDimitry Andric 
980b57cec5SDimitry Andric   // Sinks are higher importance bugs as well as calls to assert() or exit(0).
990b57cec5SDimitry Andric   LeakBugType.reset(
1000b57cec5SDimitry Andric       new BugType(this, "Resource Leak", "Unix Stream API Error",
1010b57cec5SDimitry Andric                   /*SuppressOnSink=*/true));
1020b57cec5SDimitry Andric }
1030b57cec5SDimitry Andric 
1040b57cec5SDimitry Andric void SimpleStreamChecker::checkPostCall(const CallEvent &Call,
1050b57cec5SDimitry Andric                                         CheckerContext &C) const {
1060b57cec5SDimitry Andric   if (!Call.isGlobalCFunction())
1070b57cec5SDimitry Andric     return;
1080b57cec5SDimitry Andric 
109349cc55cSDimitry Andric   if (!OpenFn.matches(Call))
1100b57cec5SDimitry Andric     return;
1110b57cec5SDimitry Andric 
1120b57cec5SDimitry Andric   // Get the symbolic value corresponding to the file handle.
1130b57cec5SDimitry Andric   SymbolRef FileDesc = Call.getReturnValue().getAsSymbol();
1140b57cec5SDimitry Andric   if (!FileDesc)
1150b57cec5SDimitry Andric     return;
1160b57cec5SDimitry Andric 
1170b57cec5SDimitry Andric   // Generate the next transition (an edge in the exploded graph).
1180b57cec5SDimitry Andric   ProgramStateRef State = C.getState();
1190b57cec5SDimitry Andric   State = State->set<StreamMap>(FileDesc, StreamState::getOpened());
1200b57cec5SDimitry Andric   C.addTransition(State);
1210b57cec5SDimitry Andric }
1220b57cec5SDimitry Andric 
1230b57cec5SDimitry Andric void SimpleStreamChecker::checkPreCall(const CallEvent &Call,
1240b57cec5SDimitry Andric                                        CheckerContext &C) const {
1250b57cec5SDimitry Andric   if (!Call.isGlobalCFunction())
1260b57cec5SDimitry Andric     return;
1270b57cec5SDimitry Andric 
128349cc55cSDimitry Andric   if (!CloseFn.matches(Call))
1290b57cec5SDimitry Andric     return;
1300b57cec5SDimitry Andric 
1310b57cec5SDimitry Andric   // Get the symbolic value corresponding to the file handle.
1320b57cec5SDimitry Andric   SymbolRef FileDesc = Call.getArgSVal(0).getAsSymbol();
1330b57cec5SDimitry Andric   if (!FileDesc)
1340b57cec5SDimitry Andric     return;
1350b57cec5SDimitry Andric 
1360b57cec5SDimitry Andric   // Check if the stream has already been closed.
1370b57cec5SDimitry Andric   ProgramStateRef State = C.getState();
1380b57cec5SDimitry Andric   const StreamState *SS = State->get<StreamMap>(FileDesc);
1390b57cec5SDimitry Andric   if (SS && SS->isClosed()) {
1400b57cec5SDimitry Andric     reportDoubleClose(FileDesc, Call, C);
1410b57cec5SDimitry Andric     return;
1420b57cec5SDimitry Andric   }
1430b57cec5SDimitry Andric 
1440b57cec5SDimitry Andric   // Generate the next transition, in which the stream is closed.
1450b57cec5SDimitry Andric   State = State->set<StreamMap>(FileDesc, StreamState::getClosed());
1460b57cec5SDimitry Andric   C.addTransition(State);
1470b57cec5SDimitry Andric }
1480b57cec5SDimitry Andric 
1490b57cec5SDimitry Andric static bool isLeaked(SymbolRef Sym, const StreamState &SS,
1500b57cec5SDimitry Andric                      bool IsSymDead, ProgramStateRef State) {
1510b57cec5SDimitry Andric   if (IsSymDead && SS.isOpened()) {
1520b57cec5SDimitry Andric     // If a symbol is NULL, assume that fopen failed on this path.
1530b57cec5SDimitry Andric     // A symbol should only be considered leaked if it is non-null.
1540b57cec5SDimitry Andric     ConstraintManager &CMgr = State->getConstraintManager();
1550b57cec5SDimitry Andric     ConditionTruthVal OpenFailed = CMgr.isNull(State, Sym);
1560b57cec5SDimitry Andric     return !OpenFailed.isConstrainedTrue();
1570b57cec5SDimitry Andric   }
1580b57cec5SDimitry Andric   return false;
1590b57cec5SDimitry Andric }
1600b57cec5SDimitry Andric 
1610b57cec5SDimitry Andric void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
1620b57cec5SDimitry Andric                                            CheckerContext &C) const {
1630b57cec5SDimitry Andric   ProgramStateRef State = C.getState();
1640b57cec5SDimitry Andric   SymbolVector LeakedStreams;
1650b57cec5SDimitry Andric   StreamMapTy TrackedStreams = State->get<StreamMap>();
166*06c3fb27SDimitry Andric   for (auto [Sym, StreamStatus] : TrackedStreams) {
1670b57cec5SDimitry Andric     bool IsSymDead = SymReaper.isDead(Sym);
1680b57cec5SDimitry Andric 
1690b57cec5SDimitry Andric     // Collect leaked symbols.
170*06c3fb27SDimitry Andric     if (isLeaked(Sym, StreamStatus, IsSymDead, State))
1710b57cec5SDimitry Andric       LeakedStreams.push_back(Sym);
1720b57cec5SDimitry Andric 
1730b57cec5SDimitry Andric     // Remove the dead symbol from the streams map.
1740b57cec5SDimitry Andric     if (IsSymDead)
1750b57cec5SDimitry Andric       State = State->remove<StreamMap>(Sym);
1760b57cec5SDimitry Andric   }
1770b57cec5SDimitry Andric 
1780b57cec5SDimitry Andric   ExplodedNode *N = C.generateNonFatalErrorNode(State);
1790b57cec5SDimitry Andric   if (!N)
1800b57cec5SDimitry Andric     return;
1810b57cec5SDimitry Andric   reportLeaks(LeakedStreams, C, N);
1820b57cec5SDimitry Andric }
1830b57cec5SDimitry Andric 
1840b57cec5SDimitry Andric void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym,
1850b57cec5SDimitry Andric                                             const CallEvent &Call,
1860b57cec5SDimitry Andric                                             CheckerContext &C) const {
1870b57cec5SDimitry Andric   // We reached a bug, stop exploring the path here by generating a sink.
1880b57cec5SDimitry Andric   ExplodedNode *ErrNode = C.generateErrorNode();
1890b57cec5SDimitry Andric   // If we've already reached this node on another path, return.
1900b57cec5SDimitry Andric   if (!ErrNode)
1910b57cec5SDimitry Andric     return;
1920b57cec5SDimitry Andric 
1930b57cec5SDimitry Andric   // Generate the report.
194a7dea167SDimitry Andric   auto R = std::make_unique<PathSensitiveBugReport>(
195a7dea167SDimitry Andric       *DoubleCloseBugType, "Closing a previously closed file stream", ErrNode);
1960b57cec5SDimitry Andric   R->addRange(Call.getSourceRange());
1970b57cec5SDimitry Andric   R->markInteresting(FileDescSym);
1980b57cec5SDimitry Andric   C.emitReport(std::move(R));
1990b57cec5SDimitry Andric }
2000b57cec5SDimitry Andric 
2010b57cec5SDimitry Andric void SimpleStreamChecker::reportLeaks(ArrayRef<SymbolRef> LeakedStreams,
2020b57cec5SDimitry Andric                                       CheckerContext &C,
2030b57cec5SDimitry Andric                                       ExplodedNode *ErrNode) const {
2040b57cec5SDimitry Andric   // Attach bug reports to the leak node.
2050b57cec5SDimitry Andric   // TODO: Identify the leaked file descriptor.
2060b57cec5SDimitry Andric   for (SymbolRef LeakedStream : LeakedStreams) {
207a7dea167SDimitry Andric     auto R = std::make_unique<PathSensitiveBugReport>(
208a7dea167SDimitry Andric         *LeakBugType, "Opened file is never closed; potential resource leak",
209a7dea167SDimitry Andric         ErrNode);
2100b57cec5SDimitry Andric     R->markInteresting(LeakedStream);
2110b57cec5SDimitry Andric     C.emitReport(std::move(R));
2120b57cec5SDimitry Andric   }
2130b57cec5SDimitry Andric }
2140b57cec5SDimitry Andric 
2150b57cec5SDimitry Andric bool SimpleStreamChecker::guaranteedNotToCloseFile(const CallEvent &Call) const{
2160b57cec5SDimitry Andric   // If it's not in a system header, assume it might close a file.
2170b57cec5SDimitry Andric   if (!Call.isInSystemHeader())
2180b57cec5SDimitry Andric     return false;
2190b57cec5SDimitry Andric 
2200b57cec5SDimitry Andric   // Handle cases where we know a buffer's /address/ can escape.
2210b57cec5SDimitry Andric   if (Call.argumentsMayEscape())
2220b57cec5SDimitry Andric     return false;
2230b57cec5SDimitry Andric 
2240b57cec5SDimitry Andric   // Note, even though fclose closes the file, we do not list it here
2250b57cec5SDimitry Andric   // since the checker is modeling the call.
2260b57cec5SDimitry Andric 
2270b57cec5SDimitry Andric   return true;
2280b57cec5SDimitry Andric }
2290b57cec5SDimitry Andric 
2300b57cec5SDimitry Andric // If the pointer we are tracking escaped, do not track the symbol as
2310b57cec5SDimitry Andric // we cannot reason about it anymore.
2320b57cec5SDimitry Andric ProgramStateRef
2330b57cec5SDimitry Andric SimpleStreamChecker::checkPointerEscape(ProgramStateRef State,
2340b57cec5SDimitry Andric                                         const InvalidatedSymbols &Escaped,
2350b57cec5SDimitry Andric                                         const CallEvent *Call,
2360b57cec5SDimitry Andric                                         PointerEscapeKind Kind) const {
2370b57cec5SDimitry Andric   // If we know that the call cannot close a file, there is nothing to do.
2380b57cec5SDimitry Andric   if (Kind == PSK_DirectEscapeOnCall && guaranteedNotToCloseFile(*Call)) {
2390b57cec5SDimitry Andric     return State;
2400b57cec5SDimitry Andric   }
2410b57cec5SDimitry Andric 
242*06c3fb27SDimitry Andric   for (SymbolRef Sym : Escaped) {
2430b57cec5SDimitry Andric     // The symbol escaped. Optimistically, assume that the corresponding file
2440b57cec5SDimitry Andric     // handle will be closed somewhere else.
2450b57cec5SDimitry Andric     State = State->remove<StreamMap>(Sym);
2460b57cec5SDimitry Andric   }
2470b57cec5SDimitry Andric   return State;
2480b57cec5SDimitry Andric }
2490b57cec5SDimitry Andric 
2500b57cec5SDimitry Andric void ento::registerSimpleStreamChecker(CheckerManager &mgr) {
2510b57cec5SDimitry Andric   mgr.registerChecker<SimpleStreamChecker>();
2520b57cec5SDimitry Andric }
2530b57cec5SDimitry Andric 
2540b57cec5SDimitry Andric // This checker should be enabled regardless of how language options are set.
2555ffd83dbSDimitry Andric bool ento::shouldRegisterSimpleStreamChecker(const CheckerManager &mgr) {
2560b57cec5SDimitry Andric   return true;
2570b57cec5SDimitry Andric }
258