xref: /freebsd/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp (revision 349cc55c9796c4596a5b9904cd3281af295f878f)
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"
20*349cc55cSDimitry 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 namespace {
930b57cec5SDimitry Andric class StopTrackingCallback final : public SymbolVisitor {
940b57cec5SDimitry Andric   ProgramStateRef state;
950b57cec5SDimitry Andric public:
960b57cec5SDimitry Andric   StopTrackingCallback(ProgramStateRef st) : state(std::move(st)) {}
970b57cec5SDimitry Andric   ProgramStateRef getState() const { return state; }
980b57cec5SDimitry Andric 
990b57cec5SDimitry Andric   bool VisitSymbol(SymbolRef sym) override {
1000b57cec5SDimitry Andric     state = state->remove<StreamMap>(sym);
1010b57cec5SDimitry Andric     return true;
1020b57cec5SDimitry Andric   }
1030b57cec5SDimitry Andric };
1040b57cec5SDimitry Andric } // end anonymous namespace
1050b57cec5SDimitry Andric 
1060b57cec5SDimitry Andric SimpleStreamChecker::SimpleStreamChecker()
1070b57cec5SDimitry Andric     : OpenFn("fopen"), CloseFn("fclose", 1) {
1080b57cec5SDimitry Andric   // Initialize the bug types.
1090b57cec5SDimitry Andric   DoubleCloseBugType.reset(
1100b57cec5SDimitry Andric       new BugType(this, "Double fclose", "Unix Stream API Error"));
1110b57cec5SDimitry Andric 
1120b57cec5SDimitry Andric   // Sinks are higher importance bugs as well as calls to assert() or exit(0).
1130b57cec5SDimitry Andric   LeakBugType.reset(
1140b57cec5SDimitry Andric       new BugType(this, "Resource Leak", "Unix Stream API Error",
1150b57cec5SDimitry Andric                   /*SuppressOnSink=*/true));
1160b57cec5SDimitry Andric }
1170b57cec5SDimitry Andric 
1180b57cec5SDimitry Andric void SimpleStreamChecker::checkPostCall(const CallEvent &Call,
1190b57cec5SDimitry Andric                                         CheckerContext &C) const {
1200b57cec5SDimitry Andric   if (!Call.isGlobalCFunction())
1210b57cec5SDimitry Andric     return;
1220b57cec5SDimitry Andric 
123*349cc55cSDimitry Andric   if (!OpenFn.matches(Call))
1240b57cec5SDimitry Andric     return;
1250b57cec5SDimitry Andric 
1260b57cec5SDimitry Andric   // Get the symbolic value corresponding to the file handle.
1270b57cec5SDimitry Andric   SymbolRef FileDesc = Call.getReturnValue().getAsSymbol();
1280b57cec5SDimitry Andric   if (!FileDesc)
1290b57cec5SDimitry Andric     return;
1300b57cec5SDimitry Andric 
1310b57cec5SDimitry Andric   // Generate the next transition (an edge in the exploded graph).
1320b57cec5SDimitry Andric   ProgramStateRef State = C.getState();
1330b57cec5SDimitry Andric   State = State->set<StreamMap>(FileDesc, StreamState::getOpened());
1340b57cec5SDimitry Andric   C.addTransition(State);
1350b57cec5SDimitry Andric }
1360b57cec5SDimitry Andric 
1370b57cec5SDimitry Andric void SimpleStreamChecker::checkPreCall(const CallEvent &Call,
1380b57cec5SDimitry Andric                                        CheckerContext &C) const {
1390b57cec5SDimitry Andric   if (!Call.isGlobalCFunction())
1400b57cec5SDimitry Andric     return;
1410b57cec5SDimitry Andric 
142*349cc55cSDimitry Andric   if (!CloseFn.matches(Call))
1430b57cec5SDimitry Andric     return;
1440b57cec5SDimitry Andric 
1450b57cec5SDimitry Andric   // Get the symbolic value corresponding to the file handle.
1460b57cec5SDimitry Andric   SymbolRef FileDesc = Call.getArgSVal(0).getAsSymbol();
1470b57cec5SDimitry Andric   if (!FileDesc)
1480b57cec5SDimitry Andric     return;
1490b57cec5SDimitry Andric 
1500b57cec5SDimitry Andric   // Check if the stream has already been closed.
1510b57cec5SDimitry Andric   ProgramStateRef State = C.getState();
1520b57cec5SDimitry Andric   const StreamState *SS = State->get<StreamMap>(FileDesc);
1530b57cec5SDimitry Andric   if (SS && SS->isClosed()) {
1540b57cec5SDimitry Andric     reportDoubleClose(FileDesc, Call, C);
1550b57cec5SDimitry Andric     return;
1560b57cec5SDimitry Andric   }
1570b57cec5SDimitry Andric 
1580b57cec5SDimitry Andric   // Generate the next transition, in which the stream is closed.
1590b57cec5SDimitry Andric   State = State->set<StreamMap>(FileDesc, StreamState::getClosed());
1600b57cec5SDimitry Andric   C.addTransition(State);
1610b57cec5SDimitry Andric }
1620b57cec5SDimitry Andric 
1630b57cec5SDimitry Andric static bool isLeaked(SymbolRef Sym, const StreamState &SS,
1640b57cec5SDimitry Andric                      bool IsSymDead, ProgramStateRef State) {
1650b57cec5SDimitry Andric   if (IsSymDead && SS.isOpened()) {
1660b57cec5SDimitry Andric     // If a symbol is NULL, assume that fopen failed on this path.
1670b57cec5SDimitry Andric     // A symbol should only be considered leaked if it is non-null.
1680b57cec5SDimitry Andric     ConstraintManager &CMgr = State->getConstraintManager();
1690b57cec5SDimitry Andric     ConditionTruthVal OpenFailed = CMgr.isNull(State, Sym);
1700b57cec5SDimitry Andric     return !OpenFailed.isConstrainedTrue();
1710b57cec5SDimitry Andric   }
1720b57cec5SDimitry Andric   return false;
1730b57cec5SDimitry Andric }
1740b57cec5SDimitry Andric 
1750b57cec5SDimitry Andric void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
1760b57cec5SDimitry Andric                                            CheckerContext &C) const {
1770b57cec5SDimitry Andric   ProgramStateRef State = C.getState();
1780b57cec5SDimitry Andric   SymbolVector LeakedStreams;
1790b57cec5SDimitry Andric   StreamMapTy TrackedStreams = State->get<StreamMap>();
1800b57cec5SDimitry Andric   for (StreamMapTy::iterator I = TrackedStreams.begin(),
1810b57cec5SDimitry Andric                              E = TrackedStreams.end(); I != E; ++I) {
1820b57cec5SDimitry Andric     SymbolRef Sym = I->first;
1830b57cec5SDimitry Andric     bool IsSymDead = SymReaper.isDead(Sym);
1840b57cec5SDimitry Andric 
1850b57cec5SDimitry Andric     // Collect leaked symbols.
1860b57cec5SDimitry Andric     if (isLeaked(Sym, I->second, IsSymDead, State))
1870b57cec5SDimitry Andric       LeakedStreams.push_back(Sym);
1880b57cec5SDimitry Andric 
1890b57cec5SDimitry Andric     // Remove the dead symbol from the streams map.
1900b57cec5SDimitry Andric     if (IsSymDead)
1910b57cec5SDimitry Andric       State = State->remove<StreamMap>(Sym);
1920b57cec5SDimitry Andric   }
1930b57cec5SDimitry Andric 
1940b57cec5SDimitry Andric   ExplodedNode *N = C.generateNonFatalErrorNode(State);
1950b57cec5SDimitry Andric   if (!N)
1960b57cec5SDimitry Andric     return;
1970b57cec5SDimitry Andric   reportLeaks(LeakedStreams, C, N);
1980b57cec5SDimitry Andric }
1990b57cec5SDimitry Andric 
2000b57cec5SDimitry Andric void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym,
2010b57cec5SDimitry Andric                                             const CallEvent &Call,
2020b57cec5SDimitry Andric                                             CheckerContext &C) const {
2030b57cec5SDimitry Andric   // We reached a bug, stop exploring the path here by generating a sink.
2040b57cec5SDimitry Andric   ExplodedNode *ErrNode = C.generateErrorNode();
2050b57cec5SDimitry Andric   // If we've already reached this node on another path, return.
2060b57cec5SDimitry Andric   if (!ErrNode)
2070b57cec5SDimitry Andric     return;
2080b57cec5SDimitry Andric 
2090b57cec5SDimitry Andric   // Generate the report.
210a7dea167SDimitry Andric   auto R = std::make_unique<PathSensitiveBugReport>(
211a7dea167SDimitry Andric       *DoubleCloseBugType, "Closing a previously closed file stream", ErrNode);
2120b57cec5SDimitry Andric   R->addRange(Call.getSourceRange());
2130b57cec5SDimitry Andric   R->markInteresting(FileDescSym);
2140b57cec5SDimitry Andric   C.emitReport(std::move(R));
2150b57cec5SDimitry Andric }
2160b57cec5SDimitry Andric 
2170b57cec5SDimitry Andric void SimpleStreamChecker::reportLeaks(ArrayRef<SymbolRef> LeakedStreams,
2180b57cec5SDimitry Andric                                       CheckerContext &C,
2190b57cec5SDimitry Andric                                       ExplodedNode *ErrNode) const {
2200b57cec5SDimitry Andric   // Attach bug reports to the leak node.
2210b57cec5SDimitry Andric   // TODO: Identify the leaked file descriptor.
2220b57cec5SDimitry Andric   for (SymbolRef LeakedStream : LeakedStreams) {
223a7dea167SDimitry Andric     auto R = std::make_unique<PathSensitiveBugReport>(
224a7dea167SDimitry Andric         *LeakBugType, "Opened file is never closed; potential resource leak",
225a7dea167SDimitry Andric         ErrNode);
2260b57cec5SDimitry Andric     R->markInteresting(LeakedStream);
2270b57cec5SDimitry Andric     C.emitReport(std::move(R));
2280b57cec5SDimitry Andric   }
2290b57cec5SDimitry Andric }
2300b57cec5SDimitry Andric 
2310b57cec5SDimitry Andric bool SimpleStreamChecker::guaranteedNotToCloseFile(const CallEvent &Call) const{
2320b57cec5SDimitry Andric   // If it's not in a system header, assume it might close a file.
2330b57cec5SDimitry Andric   if (!Call.isInSystemHeader())
2340b57cec5SDimitry Andric     return false;
2350b57cec5SDimitry Andric 
2360b57cec5SDimitry Andric   // Handle cases where we know a buffer's /address/ can escape.
2370b57cec5SDimitry Andric   if (Call.argumentsMayEscape())
2380b57cec5SDimitry Andric     return false;
2390b57cec5SDimitry Andric 
2400b57cec5SDimitry Andric   // Note, even though fclose closes the file, we do not list it here
2410b57cec5SDimitry Andric   // since the checker is modeling the call.
2420b57cec5SDimitry Andric 
2430b57cec5SDimitry Andric   return true;
2440b57cec5SDimitry Andric }
2450b57cec5SDimitry Andric 
2460b57cec5SDimitry Andric // If the pointer we are tracking escaped, do not track the symbol as
2470b57cec5SDimitry Andric // we cannot reason about it anymore.
2480b57cec5SDimitry Andric ProgramStateRef
2490b57cec5SDimitry Andric SimpleStreamChecker::checkPointerEscape(ProgramStateRef State,
2500b57cec5SDimitry Andric                                         const InvalidatedSymbols &Escaped,
2510b57cec5SDimitry Andric                                         const CallEvent *Call,
2520b57cec5SDimitry Andric                                         PointerEscapeKind Kind) const {
2530b57cec5SDimitry Andric   // If we know that the call cannot close a file, there is nothing to do.
2540b57cec5SDimitry Andric   if (Kind == PSK_DirectEscapeOnCall && guaranteedNotToCloseFile(*Call)) {
2550b57cec5SDimitry Andric     return State;
2560b57cec5SDimitry Andric   }
2570b57cec5SDimitry Andric 
2580b57cec5SDimitry Andric   for (InvalidatedSymbols::const_iterator I = Escaped.begin(),
2590b57cec5SDimitry Andric                                           E = Escaped.end();
2600b57cec5SDimitry Andric                                           I != E; ++I) {
2610b57cec5SDimitry Andric     SymbolRef Sym = *I;
2620b57cec5SDimitry Andric 
2630b57cec5SDimitry Andric     // The symbol escaped. Optimistically, assume that the corresponding file
2640b57cec5SDimitry Andric     // handle will be closed somewhere else.
2650b57cec5SDimitry Andric     State = State->remove<StreamMap>(Sym);
2660b57cec5SDimitry Andric   }
2670b57cec5SDimitry Andric   return State;
2680b57cec5SDimitry Andric }
2690b57cec5SDimitry Andric 
2700b57cec5SDimitry Andric void ento::registerSimpleStreamChecker(CheckerManager &mgr) {
2710b57cec5SDimitry Andric   mgr.registerChecker<SimpleStreamChecker>();
2720b57cec5SDimitry Andric }
2730b57cec5SDimitry Andric 
2740b57cec5SDimitry Andric // This checker should be enabled regardless of how language options are set.
2755ffd83dbSDimitry Andric bool ento::shouldRegisterSimpleStreamChecker(const CheckerManager &mgr) {
2760b57cec5SDimitry Andric   return true;
2770b57cec5SDimitry Andric }
278