xref: /freebsd/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp (revision 647cbc5de815c5651677bf8582797f716ec7b48d)
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> {
55*647cbc5dSDimitry Andric   const CallDescription OpenFn{{"fopen"}, 2};
56*647cbc5dSDimitry Andric   const CallDescription CloseFn{{"fclose"}, 1};
570b57cec5SDimitry Andric 
58*647cbc5dSDimitry Andric   const BugType DoubleCloseBugType{this, "Double fclose",
59*647cbc5dSDimitry Andric                                    "Unix Stream API Error"};
60*647cbc5dSDimitry Andric   const BugType LeakBugType{this, "Resource Leak", "Unix Stream API Error",
61*647cbc5dSDimitry Andric                             /*SuppressOnSink=*/true};
620b57cec5SDimitry Andric 
630b57cec5SDimitry Andric   void reportDoubleClose(SymbolRef FileDescSym,
640b57cec5SDimitry Andric                          const CallEvent &Call,
650b57cec5SDimitry Andric                          CheckerContext &C) const;
660b57cec5SDimitry Andric 
670b57cec5SDimitry Andric   void reportLeaks(ArrayRef<SymbolRef> LeakedStreams, CheckerContext &C,
680b57cec5SDimitry Andric                    ExplodedNode *ErrNode) const;
690b57cec5SDimitry Andric 
700b57cec5SDimitry Andric   bool guaranteedNotToCloseFile(const CallEvent &Call) const;
710b57cec5SDimitry Andric 
720b57cec5SDimitry Andric public:
730b57cec5SDimitry Andric   /// Process fopen.
740b57cec5SDimitry Andric   void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
750b57cec5SDimitry Andric   /// Process fclose.
760b57cec5SDimitry Andric   void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
770b57cec5SDimitry Andric 
780b57cec5SDimitry Andric   void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
790b57cec5SDimitry Andric 
800b57cec5SDimitry Andric   /// Stop tracking addresses which escape.
810b57cec5SDimitry Andric   ProgramStateRef checkPointerEscape(ProgramStateRef State,
820b57cec5SDimitry Andric                                     const InvalidatedSymbols &Escaped,
830b57cec5SDimitry Andric                                     const CallEvent *Call,
840b57cec5SDimitry Andric                                     PointerEscapeKind Kind) const;
850b57cec5SDimitry Andric };
860b57cec5SDimitry Andric 
870b57cec5SDimitry Andric } // end anonymous namespace
880b57cec5SDimitry Andric 
890b57cec5SDimitry Andric /// The state of the checker is a map from tracked stream symbols to their
900b57cec5SDimitry Andric /// state. Let's store it in the ProgramState.
910b57cec5SDimitry Andric REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
920b57cec5SDimitry Andric 
930b57cec5SDimitry Andric void SimpleStreamChecker::checkPostCall(const CallEvent &Call,
940b57cec5SDimitry Andric                                         CheckerContext &C) const {
950b57cec5SDimitry Andric   if (!Call.isGlobalCFunction())
960b57cec5SDimitry Andric     return;
970b57cec5SDimitry Andric 
98349cc55cSDimitry Andric   if (!OpenFn.matches(Call))
990b57cec5SDimitry Andric     return;
1000b57cec5SDimitry Andric 
1010b57cec5SDimitry Andric   // Get the symbolic value corresponding to the file handle.
1020b57cec5SDimitry Andric   SymbolRef FileDesc = Call.getReturnValue().getAsSymbol();
1030b57cec5SDimitry Andric   if (!FileDesc)
1040b57cec5SDimitry Andric     return;
1050b57cec5SDimitry Andric 
1060b57cec5SDimitry Andric   // Generate the next transition (an edge in the exploded graph).
1070b57cec5SDimitry Andric   ProgramStateRef State = C.getState();
1080b57cec5SDimitry Andric   State = State->set<StreamMap>(FileDesc, StreamState::getOpened());
1090b57cec5SDimitry Andric   C.addTransition(State);
1100b57cec5SDimitry Andric }
1110b57cec5SDimitry Andric 
1120b57cec5SDimitry Andric void SimpleStreamChecker::checkPreCall(const CallEvent &Call,
1130b57cec5SDimitry Andric                                        CheckerContext &C) const {
1140b57cec5SDimitry Andric   if (!Call.isGlobalCFunction())
1150b57cec5SDimitry Andric     return;
1160b57cec5SDimitry Andric 
117349cc55cSDimitry Andric   if (!CloseFn.matches(Call))
1180b57cec5SDimitry Andric     return;
1190b57cec5SDimitry Andric 
1200b57cec5SDimitry Andric   // Get the symbolic value corresponding to the file handle.
1210b57cec5SDimitry Andric   SymbolRef FileDesc = Call.getArgSVal(0).getAsSymbol();
1220b57cec5SDimitry Andric   if (!FileDesc)
1230b57cec5SDimitry Andric     return;
1240b57cec5SDimitry Andric 
1250b57cec5SDimitry Andric   // Check if the stream has already been closed.
1260b57cec5SDimitry Andric   ProgramStateRef State = C.getState();
1270b57cec5SDimitry Andric   const StreamState *SS = State->get<StreamMap>(FileDesc);
1280b57cec5SDimitry Andric   if (SS && SS->isClosed()) {
1290b57cec5SDimitry Andric     reportDoubleClose(FileDesc, Call, C);
1300b57cec5SDimitry Andric     return;
1310b57cec5SDimitry Andric   }
1320b57cec5SDimitry Andric 
1330b57cec5SDimitry Andric   // Generate the next transition, in which the stream is closed.
1340b57cec5SDimitry Andric   State = State->set<StreamMap>(FileDesc, StreamState::getClosed());
1350b57cec5SDimitry Andric   C.addTransition(State);
1360b57cec5SDimitry Andric }
1370b57cec5SDimitry Andric 
1380b57cec5SDimitry Andric static bool isLeaked(SymbolRef Sym, const StreamState &SS,
1390b57cec5SDimitry Andric                      bool IsSymDead, ProgramStateRef State) {
1400b57cec5SDimitry Andric   if (IsSymDead && SS.isOpened()) {
1410b57cec5SDimitry Andric     // If a symbol is NULL, assume that fopen failed on this path.
1420b57cec5SDimitry Andric     // A symbol should only be considered leaked if it is non-null.
1430b57cec5SDimitry Andric     ConstraintManager &CMgr = State->getConstraintManager();
1440b57cec5SDimitry Andric     ConditionTruthVal OpenFailed = CMgr.isNull(State, Sym);
1450b57cec5SDimitry Andric     return !OpenFailed.isConstrainedTrue();
1460b57cec5SDimitry Andric   }
1470b57cec5SDimitry Andric   return false;
1480b57cec5SDimitry Andric }
1490b57cec5SDimitry Andric 
1500b57cec5SDimitry Andric void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
1510b57cec5SDimitry Andric                                            CheckerContext &C) const {
1520b57cec5SDimitry Andric   ProgramStateRef State = C.getState();
1530b57cec5SDimitry Andric   SymbolVector LeakedStreams;
1540b57cec5SDimitry Andric   StreamMapTy TrackedStreams = State->get<StreamMap>();
15506c3fb27SDimitry Andric   for (auto [Sym, StreamStatus] : TrackedStreams) {
1560b57cec5SDimitry Andric     bool IsSymDead = SymReaper.isDead(Sym);
1570b57cec5SDimitry Andric 
1580b57cec5SDimitry Andric     // Collect leaked symbols.
15906c3fb27SDimitry Andric     if (isLeaked(Sym, StreamStatus, IsSymDead, State))
1600b57cec5SDimitry Andric       LeakedStreams.push_back(Sym);
1610b57cec5SDimitry Andric 
1620b57cec5SDimitry Andric     // Remove the dead symbol from the streams map.
1630b57cec5SDimitry Andric     if (IsSymDead)
1640b57cec5SDimitry Andric       State = State->remove<StreamMap>(Sym);
1650b57cec5SDimitry Andric   }
1660b57cec5SDimitry Andric 
1670b57cec5SDimitry Andric   ExplodedNode *N = C.generateNonFatalErrorNode(State);
1680b57cec5SDimitry Andric   if (!N)
1690b57cec5SDimitry Andric     return;
1700b57cec5SDimitry Andric   reportLeaks(LeakedStreams, C, N);
1710b57cec5SDimitry Andric }
1720b57cec5SDimitry Andric 
1730b57cec5SDimitry Andric void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym,
1740b57cec5SDimitry Andric                                             const CallEvent &Call,
1750b57cec5SDimitry Andric                                             CheckerContext &C) const {
1760b57cec5SDimitry Andric   // We reached a bug, stop exploring the path here by generating a sink.
1770b57cec5SDimitry Andric   ExplodedNode *ErrNode = C.generateErrorNode();
1780b57cec5SDimitry Andric   // If we've already reached this node on another path, return.
1790b57cec5SDimitry Andric   if (!ErrNode)
1800b57cec5SDimitry Andric     return;
1810b57cec5SDimitry Andric 
1820b57cec5SDimitry Andric   // Generate the report.
183a7dea167SDimitry Andric   auto R = std::make_unique<PathSensitiveBugReport>(
184*647cbc5dSDimitry Andric       DoubleCloseBugType, "Closing a previously closed file stream", ErrNode);
1850b57cec5SDimitry Andric   R->addRange(Call.getSourceRange());
1860b57cec5SDimitry Andric   R->markInteresting(FileDescSym);
1870b57cec5SDimitry Andric   C.emitReport(std::move(R));
1880b57cec5SDimitry Andric }
1890b57cec5SDimitry Andric 
1900b57cec5SDimitry Andric void SimpleStreamChecker::reportLeaks(ArrayRef<SymbolRef> LeakedStreams,
1910b57cec5SDimitry Andric                                       CheckerContext &C,
1920b57cec5SDimitry Andric                                       ExplodedNode *ErrNode) const {
1930b57cec5SDimitry Andric   // Attach bug reports to the leak node.
1940b57cec5SDimitry Andric   // TODO: Identify the leaked file descriptor.
1950b57cec5SDimitry Andric   for (SymbolRef LeakedStream : LeakedStreams) {
196a7dea167SDimitry Andric     auto R = std::make_unique<PathSensitiveBugReport>(
197*647cbc5dSDimitry Andric         LeakBugType, "Opened file is never closed; potential resource leak",
198a7dea167SDimitry Andric         ErrNode);
1990b57cec5SDimitry Andric     R->markInteresting(LeakedStream);
2000b57cec5SDimitry Andric     C.emitReport(std::move(R));
2010b57cec5SDimitry Andric   }
2020b57cec5SDimitry Andric }
2030b57cec5SDimitry Andric 
2040b57cec5SDimitry Andric bool SimpleStreamChecker::guaranteedNotToCloseFile(const CallEvent &Call) const{
2050b57cec5SDimitry Andric   // If it's not in a system header, assume it might close a file.
2060b57cec5SDimitry Andric   if (!Call.isInSystemHeader())
2070b57cec5SDimitry Andric     return false;
2080b57cec5SDimitry Andric 
2090b57cec5SDimitry Andric   // Handle cases where we know a buffer's /address/ can escape.
2100b57cec5SDimitry Andric   if (Call.argumentsMayEscape())
2110b57cec5SDimitry Andric     return false;
2120b57cec5SDimitry Andric 
2130b57cec5SDimitry Andric   // Note, even though fclose closes the file, we do not list it here
2140b57cec5SDimitry Andric   // since the checker is modeling the call.
2150b57cec5SDimitry Andric 
2160b57cec5SDimitry Andric   return true;
2170b57cec5SDimitry Andric }
2180b57cec5SDimitry Andric 
2190b57cec5SDimitry Andric // If the pointer we are tracking escaped, do not track the symbol as
2200b57cec5SDimitry Andric // we cannot reason about it anymore.
2210b57cec5SDimitry Andric ProgramStateRef
2220b57cec5SDimitry Andric SimpleStreamChecker::checkPointerEscape(ProgramStateRef State,
2230b57cec5SDimitry Andric                                         const InvalidatedSymbols &Escaped,
2240b57cec5SDimitry Andric                                         const CallEvent *Call,
2250b57cec5SDimitry Andric                                         PointerEscapeKind Kind) const {
2260b57cec5SDimitry Andric   // If we know that the call cannot close a file, there is nothing to do.
2270b57cec5SDimitry Andric   if (Kind == PSK_DirectEscapeOnCall && guaranteedNotToCloseFile(*Call)) {
2280b57cec5SDimitry Andric     return State;
2290b57cec5SDimitry Andric   }
2300b57cec5SDimitry Andric 
23106c3fb27SDimitry Andric   for (SymbolRef Sym : Escaped) {
2320b57cec5SDimitry Andric     // The symbol escaped. Optimistically, assume that the corresponding file
2330b57cec5SDimitry Andric     // handle will be closed somewhere else.
2340b57cec5SDimitry Andric     State = State->remove<StreamMap>(Sym);
2350b57cec5SDimitry Andric   }
2360b57cec5SDimitry Andric   return State;
2370b57cec5SDimitry Andric }
2380b57cec5SDimitry Andric 
2390b57cec5SDimitry Andric void ento::registerSimpleStreamChecker(CheckerManager &mgr) {
2400b57cec5SDimitry Andric   mgr.registerChecker<SimpleStreamChecker>();
2410b57cec5SDimitry Andric }
2420b57cec5SDimitry Andric 
2430b57cec5SDimitry Andric // This checker should be enabled regardless of how language options are set.
2445ffd83dbSDimitry Andric bool ento::shouldRegisterSimpleStreamChecker(const CheckerManager &mgr) {
2450b57cec5SDimitry Andric   return true;
2460b57cec5SDimitry Andric }
247