1*0fca6ea1SDimitry 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;
StreamState__anon788d88190111::StreamState340b57cec5SDimitry Andric StreamState(Kind InK) : K(InK) { }
350b57cec5SDimitry Andric
360b57cec5SDimitry Andric public:
isOpened__anon788d88190111::StreamState370b57cec5SDimitry Andric bool isOpened() const { return K == Opened; }
isClosed__anon788d88190111::StreamState380b57cec5SDimitry Andric bool isClosed() const { return K == Closed; }
390b57cec5SDimitry Andric
getOpened__anon788d88190111::StreamState400b57cec5SDimitry Andric static StreamState getOpened() { return StreamState(Opened); }
getClosed__anon788d88190111::StreamState410b57cec5SDimitry Andric static StreamState getClosed() { return StreamState(Closed); }
420b57cec5SDimitry Andric
operator ==__anon788d88190111::StreamState430b57cec5SDimitry Andric bool operator==(const StreamState &X) const {
440b57cec5SDimitry Andric return K == X.K;
450b57cec5SDimitry Andric }
Profile__anon788d88190111::StreamState460b57cec5SDimitry 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*0fca6ea1SDimitry Andric const CallDescription OpenFn{CDM::CLibrary, {"fopen"}, 2};
56*0fca6ea1SDimitry Andric const CallDescription CloseFn{CDM::CLibrary, {"fclose"}, 1};
570b57cec5SDimitry Andric
58647cbc5dSDimitry Andric const BugType DoubleCloseBugType{this, "Double fclose",
59647cbc5dSDimitry Andric "Unix Stream API Error"};
60647cbc5dSDimitry Andric const BugType LeakBugType{this, "Resource Leak", "Unix Stream API Error",
61647cbc5dSDimitry 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.
REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap,SymbolRef,StreamState)910b57cec5SDimitry Andric REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
920b57cec5SDimitry Andric
930b57cec5SDimitry Andric void SimpleStreamChecker::checkPostCall(const CallEvent &Call,
940b57cec5SDimitry Andric CheckerContext &C) const {
95349cc55cSDimitry Andric if (!OpenFn.matches(Call))
960b57cec5SDimitry Andric return;
970b57cec5SDimitry Andric
980b57cec5SDimitry Andric // Get the symbolic value corresponding to the file handle.
990b57cec5SDimitry Andric SymbolRef FileDesc = Call.getReturnValue().getAsSymbol();
1000b57cec5SDimitry Andric if (!FileDesc)
1010b57cec5SDimitry Andric return;
1020b57cec5SDimitry Andric
1030b57cec5SDimitry Andric // Generate the next transition (an edge in the exploded graph).
1040b57cec5SDimitry Andric ProgramStateRef State = C.getState();
1050b57cec5SDimitry Andric State = State->set<StreamMap>(FileDesc, StreamState::getOpened());
1060b57cec5SDimitry Andric C.addTransition(State);
1070b57cec5SDimitry Andric }
1080b57cec5SDimitry Andric
checkPreCall(const CallEvent & Call,CheckerContext & C) const1090b57cec5SDimitry Andric void SimpleStreamChecker::checkPreCall(const CallEvent &Call,
1100b57cec5SDimitry Andric CheckerContext &C) const {
111349cc55cSDimitry Andric if (!CloseFn.matches(Call))
1120b57cec5SDimitry Andric return;
1130b57cec5SDimitry Andric
1140b57cec5SDimitry Andric // Get the symbolic value corresponding to the file handle.
1150b57cec5SDimitry Andric SymbolRef FileDesc = Call.getArgSVal(0).getAsSymbol();
1160b57cec5SDimitry Andric if (!FileDesc)
1170b57cec5SDimitry Andric return;
1180b57cec5SDimitry Andric
1190b57cec5SDimitry Andric // Check if the stream has already been closed.
1200b57cec5SDimitry Andric ProgramStateRef State = C.getState();
1210b57cec5SDimitry Andric const StreamState *SS = State->get<StreamMap>(FileDesc);
1220b57cec5SDimitry Andric if (SS && SS->isClosed()) {
1230b57cec5SDimitry Andric reportDoubleClose(FileDesc, Call, C);
1240b57cec5SDimitry Andric return;
1250b57cec5SDimitry Andric }
1260b57cec5SDimitry Andric
1270b57cec5SDimitry Andric // Generate the next transition, in which the stream is closed.
1280b57cec5SDimitry Andric State = State->set<StreamMap>(FileDesc, StreamState::getClosed());
1290b57cec5SDimitry Andric C.addTransition(State);
1300b57cec5SDimitry Andric }
1310b57cec5SDimitry Andric
isLeaked(SymbolRef Sym,const StreamState & SS,bool IsSymDead,ProgramStateRef State)1320b57cec5SDimitry Andric static bool isLeaked(SymbolRef Sym, const StreamState &SS,
1330b57cec5SDimitry Andric bool IsSymDead, ProgramStateRef State) {
1340b57cec5SDimitry Andric if (IsSymDead && SS.isOpened()) {
1350b57cec5SDimitry Andric // If a symbol is NULL, assume that fopen failed on this path.
1360b57cec5SDimitry Andric // A symbol should only be considered leaked if it is non-null.
1370b57cec5SDimitry Andric ConstraintManager &CMgr = State->getConstraintManager();
1380b57cec5SDimitry Andric ConditionTruthVal OpenFailed = CMgr.isNull(State, Sym);
1390b57cec5SDimitry Andric return !OpenFailed.isConstrainedTrue();
1400b57cec5SDimitry Andric }
1410b57cec5SDimitry Andric return false;
1420b57cec5SDimitry Andric }
1430b57cec5SDimitry Andric
checkDeadSymbols(SymbolReaper & SymReaper,CheckerContext & C) const1440b57cec5SDimitry Andric void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
1450b57cec5SDimitry Andric CheckerContext &C) const {
1460b57cec5SDimitry Andric ProgramStateRef State = C.getState();
1470b57cec5SDimitry Andric SymbolVector LeakedStreams;
1480b57cec5SDimitry Andric StreamMapTy TrackedStreams = State->get<StreamMap>();
14906c3fb27SDimitry Andric for (auto [Sym, StreamStatus] : TrackedStreams) {
1500b57cec5SDimitry Andric bool IsSymDead = SymReaper.isDead(Sym);
1510b57cec5SDimitry Andric
1520b57cec5SDimitry Andric // Collect leaked symbols.
15306c3fb27SDimitry Andric if (isLeaked(Sym, StreamStatus, IsSymDead, State))
1540b57cec5SDimitry Andric LeakedStreams.push_back(Sym);
1550b57cec5SDimitry Andric
1560b57cec5SDimitry Andric // Remove the dead symbol from the streams map.
1570b57cec5SDimitry Andric if (IsSymDead)
1580b57cec5SDimitry Andric State = State->remove<StreamMap>(Sym);
1590b57cec5SDimitry Andric }
1600b57cec5SDimitry Andric
1610b57cec5SDimitry Andric ExplodedNode *N = C.generateNonFatalErrorNode(State);
1620b57cec5SDimitry Andric if (!N)
1630b57cec5SDimitry Andric return;
1640b57cec5SDimitry Andric reportLeaks(LeakedStreams, C, N);
1650b57cec5SDimitry Andric }
1660b57cec5SDimitry Andric
reportDoubleClose(SymbolRef FileDescSym,const CallEvent & Call,CheckerContext & C) const1670b57cec5SDimitry Andric void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym,
1680b57cec5SDimitry Andric const CallEvent &Call,
1690b57cec5SDimitry Andric CheckerContext &C) const {
1700b57cec5SDimitry Andric // We reached a bug, stop exploring the path here by generating a sink.
1710b57cec5SDimitry Andric ExplodedNode *ErrNode = C.generateErrorNode();
1720b57cec5SDimitry Andric // If we've already reached this node on another path, return.
1730b57cec5SDimitry Andric if (!ErrNode)
1740b57cec5SDimitry Andric return;
1750b57cec5SDimitry Andric
1760b57cec5SDimitry Andric // Generate the report.
177a7dea167SDimitry Andric auto R = std::make_unique<PathSensitiveBugReport>(
178647cbc5dSDimitry Andric DoubleCloseBugType, "Closing a previously closed file stream", ErrNode);
1790b57cec5SDimitry Andric R->addRange(Call.getSourceRange());
1800b57cec5SDimitry Andric R->markInteresting(FileDescSym);
1810b57cec5SDimitry Andric C.emitReport(std::move(R));
1820b57cec5SDimitry Andric }
1830b57cec5SDimitry Andric
reportLeaks(ArrayRef<SymbolRef> LeakedStreams,CheckerContext & C,ExplodedNode * ErrNode) const1840b57cec5SDimitry Andric void SimpleStreamChecker::reportLeaks(ArrayRef<SymbolRef> LeakedStreams,
1850b57cec5SDimitry Andric CheckerContext &C,
1860b57cec5SDimitry Andric ExplodedNode *ErrNode) const {
1870b57cec5SDimitry Andric // Attach bug reports to the leak node.
1880b57cec5SDimitry Andric // TODO: Identify the leaked file descriptor.
1890b57cec5SDimitry Andric for (SymbolRef LeakedStream : LeakedStreams) {
190a7dea167SDimitry Andric auto R = std::make_unique<PathSensitiveBugReport>(
191647cbc5dSDimitry Andric LeakBugType, "Opened file is never closed; potential resource leak",
192a7dea167SDimitry Andric ErrNode);
1930b57cec5SDimitry Andric R->markInteresting(LeakedStream);
1940b57cec5SDimitry Andric C.emitReport(std::move(R));
1950b57cec5SDimitry Andric }
1960b57cec5SDimitry Andric }
1970b57cec5SDimitry Andric
guaranteedNotToCloseFile(const CallEvent & Call) const1980b57cec5SDimitry Andric bool SimpleStreamChecker::guaranteedNotToCloseFile(const CallEvent &Call) const{
1990b57cec5SDimitry Andric // If it's not in a system header, assume it might close a file.
2000b57cec5SDimitry Andric if (!Call.isInSystemHeader())
2010b57cec5SDimitry Andric return false;
2020b57cec5SDimitry Andric
2030b57cec5SDimitry Andric // Handle cases where we know a buffer's /address/ can escape.
2040b57cec5SDimitry Andric if (Call.argumentsMayEscape())
2050b57cec5SDimitry Andric return false;
2060b57cec5SDimitry Andric
2070b57cec5SDimitry Andric // Note, even though fclose closes the file, we do not list it here
2080b57cec5SDimitry Andric // since the checker is modeling the call.
2090b57cec5SDimitry Andric
2100b57cec5SDimitry Andric return true;
2110b57cec5SDimitry Andric }
2120b57cec5SDimitry Andric
2130b57cec5SDimitry Andric // If the pointer we are tracking escaped, do not track the symbol as
2140b57cec5SDimitry Andric // we cannot reason about it anymore.
2150b57cec5SDimitry Andric ProgramStateRef
checkPointerEscape(ProgramStateRef State,const InvalidatedSymbols & Escaped,const CallEvent * Call,PointerEscapeKind Kind) const2160b57cec5SDimitry Andric SimpleStreamChecker::checkPointerEscape(ProgramStateRef State,
2170b57cec5SDimitry Andric const InvalidatedSymbols &Escaped,
2180b57cec5SDimitry Andric const CallEvent *Call,
2190b57cec5SDimitry Andric PointerEscapeKind Kind) const {
2200b57cec5SDimitry Andric // If we know that the call cannot close a file, there is nothing to do.
2210b57cec5SDimitry Andric if (Kind == PSK_DirectEscapeOnCall && guaranteedNotToCloseFile(*Call)) {
2220b57cec5SDimitry Andric return State;
2230b57cec5SDimitry Andric }
2240b57cec5SDimitry Andric
22506c3fb27SDimitry Andric for (SymbolRef Sym : Escaped) {
2260b57cec5SDimitry Andric // The symbol escaped. Optimistically, assume that the corresponding file
2270b57cec5SDimitry Andric // handle will be closed somewhere else.
2280b57cec5SDimitry Andric State = State->remove<StreamMap>(Sym);
2290b57cec5SDimitry Andric }
2300b57cec5SDimitry Andric return State;
2310b57cec5SDimitry Andric }
2320b57cec5SDimitry Andric
registerSimpleStreamChecker(CheckerManager & mgr)2330b57cec5SDimitry Andric void ento::registerSimpleStreamChecker(CheckerManager &mgr) {
2340b57cec5SDimitry Andric mgr.registerChecker<SimpleStreamChecker>();
2350b57cec5SDimitry Andric }
2360b57cec5SDimitry Andric
2370b57cec5SDimitry Andric // This checker should be enabled regardless of how language options are set.
shouldRegisterSimpleStreamChecker(const CheckerManager & mgr)2385ffd83dbSDimitry Andric bool ento::shouldRegisterSimpleStreamChecker(const CheckerManager &mgr) {
2390b57cec5SDimitry Andric return true;
2400b57cec5SDimitry Andric }
241