1243791Sdim//===-- SimpleStreamChecker.cpp -----------------------------------------*- C++ -*--// 2243791Sdim// 3353358Sdim// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4353358Sdim// See https://llvm.org/LICENSE.txt for license information. 5353358Sdim// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6243791Sdim// 7243791Sdim//===----------------------------------------------------------------------===// 8243791Sdim// 9243791Sdim// Defines a checker for proper use of fopen/fclose APIs. 10243791Sdim// - If a file has been closed with fclose, it should not be accessed again. 11243791Sdim// Accessing a closed file results in undefined behavior. 12243791Sdim// - If a file was opened with fopen, it must be closed with fclose before 13243791Sdim// the execution ends. Failing to do so results in a resource leak. 14243791Sdim// 15243791Sdim//===----------------------------------------------------------------------===// 16243791Sdim 17344779Sdim#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" 18249423Sdim#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 19243791Sdim#include "clang/StaticAnalyzer/Core/Checker.h" 20243791Sdim#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" 21243791Sdim#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 22309124Sdim#include <utility> 23243791Sdim 24243791Sdimusing namespace clang; 25243791Sdimusing namespace ento; 26243791Sdim 27243791Sdimnamespace { 28249423Sdimtypedef SmallVector<SymbolRef, 2> SymbolVector; 29243791Sdim 30243791Sdimstruct StreamState { 31243791Sdimprivate: 32243791Sdim enum Kind { Opened, Closed } K; 33243791Sdim StreamState(Kind InK) : K(InK) { } 34243791Sdim 35243791Sdimpublic: 36243791Sdim bool isOpened() const { return K == Opened; } 37243791Sdim bool isClosed() const { return K == Closed; } 38243791Sdim 39243791Sdim static StreamState getOpened() { return StreamState(Opened); } 40243791Sdim static StreamState getClosed() { return StreamState(Closed); } 41243791Sdim 42243791Sdim bool operator==(const StreamState &X) const { 43243791Sdim return K == X.K; 44243791Sdim } 45243791Sdim void Profile(llvm::FoldingSetNodeID &ID) const { 46243791Sdim ID.AddInteger(K); 47243791Sdim } 48243791Sdim}; 49243791Sdim 50243791Sdimclass SimpleStreamChecker : public Checker<check::PostCall, 51243791Sdim check::PreCall, 52243791Sdim check::DeadSymbols, 53249423Sdim check::PointerEscape> { 54309124Sdim CallDescription OpenFn, CloseFn; 55243791Sdim 56276479Sdim std::unique_ptr<BugType> DoubleCloseBugType; 57276479Sdim std::unique_ptr<BugType> LeakBugType; 58243791Sdim 59243791Sdim void reportDoubleClose(SymbolRef FileDescSym, 60243791Sdim const CallEvent &Call, 61243791Sdim CheckerContext &C) const; 62243791Sdim 63280031Sdim void reportLeaks(ArrayRef<SymbolRef> LeakedStreams, CheckerContext &C, 64243791Sdim ExplodedNode *ErrNode) const; 65243791Sdim 66243791Sdim bool guaranteedNotToCloseFile(const CallEvent &Call) const; 67243791Sdim 68243791Sdimpublic: 69243791Sdim SimpleStreamChecker(); 70243791Sdim 71243791Sdim /// Process fopen. 72243791Sdim void checkPostCall(const CallEvent &Call, CheckerContext &C) const; 73243791Sdim /// Process fclose. 74243791Sdim void checkPreCall(const CallEvent &Call, CheckerContext &C) const; 75243791Sdim 76243791Sdim void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; 77243791Sdim 78249423Sdim /// Stop tracking addresses which escape. 79249423Sdim ProgramStateRef checkPointerEscape(ProgramStateRef State, 80249423Sdim const InvalidatedSymbols &Escaped, 81249423Sdim const CallEvent *Call, 82249423Sdim PointerEscapeKind Kind) const; 83243791Sdim}; 84243791Sdim 85243791Sdim} // end anonymous namespace 86243791Sdim 87243791Sdim/// The state of the checker is a map from tracked stream symbols to their 88243791Sdim/// state. Let's store it in the ProgramState. 89243791SdimREGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) 90243791Sdim 91243791Sdimnamespace { 92296417Sdimclass StopTrackingCallback final : public SymbolVisitor { 93243791Sdim ProgramStateRef state; 94243791Sdimpublic: 95309124Sdim StopTrackingCallback(ProgramStateRef st) : state(std::move(st)) {} 96243791Sdim ProgramStateRef getState() const { return state; } 97243791Sdim 98276479Sdim bool VisitSymbol(SymbolRef sym) override { 99243791Sdim state = state->remove<StreamMap>(sym); 100243791Sdim return true; 101243791Sdim } 102243791Sdim}; 103243791Sdim} // end anonymous namespace 104243791Sdim 105276479SdimSimpleStreamChecker::SimpleStreamChecker() 106309124Sdim : OpenFn("fopen"), CloseFn("fclose", 1) { 107243791Sdim // Initialize the bug types. 108276479Sdim DoubleCloseBugType.reset( 109276479Sdim new BugType(this, "Double fclose", "Unix Stream API Error")); 110243791Sdim 111353358Sdim // Sinks are higher importance bugs as well as calls to assert() or exit(0). 112276479Sdim LeakBugType.reset( 113353358Sdim new BugType(this, "Resource Leak", "Unix Stream API Error", 114353358Sdim /*SuppressOnSink=*/true)); 115243791Sdim} 116243791Sdim 117243791Sdimvoid SimpleStreamChecker::checkPostCall(const CallEvent &Call, 118243791Sdim CheckerContext &C) const { 119243791Sdim if (!Call.isGlobalCFunction()) 120243791Sdim return; 121243791Sdim 122309124Sdim if (!Call.isCalled(OpenFn)) 123243791Sdim return; 124243791Sdim 125243791Sdim // Get the symbolic value corresponding to the file handle. 126243791Sdim SymbolRef FileDesc = Call.getReturnValue().getAsSymbol(); 127243791Sdim if (!FileDesc) 128243791Sdim return; 129243791Sdim 130243791Sdim // Generate the next transition (an edge in the exploded graph). 131243791Sdim ProgramStateRef State = C.getState(); 132243791Sdim State = State->set<StreamMap>(FileDesc, StreamState::getOpened()); 133243791Sdim C.addTransition(State); 134243791Sdim} 135243791Sdim 136243791Sdimvoid SimpleStreamChecker::checkPreCall(const CallEvent &Call, 137243791Sdim CheckerContext &C) const { 138243791Sdim if (!Call.isGlobalCFunction()) 139243791Sdim return; 140243791Sdim 141309124Sdim if (!Call.isCalled(CloseFn)) 142243791Sdim return; 143243791Sdim 144243791Sdim // Get the symbolic value corresponding to the file handle. 145243791Sdim SymbolRef FileDesc = Call.getArgSVal(0).getAsSymbol(); 146243791Sdim if (!FileDesc) 147243791Sdim return; 148243791Sdim 149243791Sdim // Check if the stream has already been closed. 150243791Sdim ProgramStateRef State = C.getState(); 151243791Sdim const StreamState *SS = State->get<StreamMap>(FileDesc); 152243791Sdim if (SS && SS->isClosed()) { 153243791Sdim reportDoubleClose(FileDesc, Call, C); 154243791Sdim return; 155243791Sdim } 156243791Sdim 157243791Sdim // Generate the next transition, in which the stream is closed. 158243791Sdim State = State->set<StreamMap>(FileDesc, StreamState::getClosed()); 159243791Sdim C.addTransition(State); 160243791Sdim} 161243791Sdim 162243791Sdimstatic bool isLeaked(SymbolRef Sym, const StreamState &SS, 163243791Sdim bool IsSymDead, ProgramStateRef State) { 164243791Sdim if (IsSymDead && SS.isOpened()) { 165243791Sdim // If a symbol is NULL, assume that fopen failed on this path. 166243791Sdim // A symbol should only be considered leaked if it is non-null. 167243791Sdim ConstraintManager &CMgr = State->getConstraintManager(); 168243791Sdim ConditionTruthVal OpenFailed = CMgr.isNull(State, Sym); 169243791Sdim return !OpenFailed.isConstrainedTrue(); 170243791Sdim } 171243791Sdim return false; 172243791Sdim} 173243791Sdim 174243791Sdimvoid SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, 175243791Sdim CheckerContext &C) const { 176243791Sdim ProgramStateRef State = C.getState(); 177243791Sdim SymbolVector LeakedStreams; 178243791Sdim StreamMapTy TrackedStreams = State->get<StreamMap>(); 179243791Sdim for (StreamMapTy::iterator I = TrackedStreams.begin(), 180243791Sdim E = TrackedStreams.end(); I != E; ++I) { 181243791Sdim SymbolRef Sym = I->first; 182243791Sdim bool IsSymDead = SymReaper.isDead(Sym); 183243791Sdim 184243791Sdim // Collect leaked symbols. 185243791Sdim if (isLeaked(Sym, I->second, IsSymDead, State)) 186243791Sdim LeakedStreams.push_back(Sym); 187243791Sdim 188243791Sdim // Remove the dead symbol from the streams map. 189243791Sdim if (IsSymDead) 190243791Sdim State = State->remove<StreamMap>(Sym); 191243791Sdim } 192243791Sdim 193296417Sdim ExplodedNode *N = C.generateNonFatalErrorNode(State); 194296417Sdim if (!N) 195296417Sdim return; 196243791Sdim reportLeaks(LeakedStreams, C, N); 197243791Sdim} 198243791Sdim 199243791Sdimvoid SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym, 200243791Sdim const CallEvent &Call, 201243791Sdim CheckerContext &C) const { 202243791Sdim // We reached a bug, stop exploring the path here by generating a sink. 203296417Sdim ExplodedNode *ErrNode = C.generateErrorNode(); 204243791Sdim // If we've already reached this node on another path, return. 205243791Sdim if (!ErrNode) 206243791Sdim return; 207243791Sdim 208243791Sdim // Generate the report. 209360784Sdim auto R = std::make_unique<PathSensitiveBugReport>( 210360784Sdim *DoubleCloseBugType, "Closing a previously closed file stream", ErrNode); 211243791Sdim R->addRange(Call.getSourceRange()); 212243791Sdim R->markInteresting(FileDescSym); 213288943Sdim C.emitReport(std::move(R)); 214243791Sdim} 215243791Sdim 216280031Sdimvoid SimpleStreamChecker::reportLeaks(ArrayRef<SymbolRef> LeakedStreams, 217280031Sdim CheckerContext &C, 218280031Sdim ExplodedNode *ErrNode) const { 219243791Sdim // Attach bug reports to the leak node. 220243791Sdim // TODO: Identify the leaked file descriptor. 221280031Sdim for (SymbolRef LeakedStream : LeakedStreams) { 222360784Sdim auto R = std::make_unique<PathSensitiveBugReport>( 223360784Sdim *LeakBugType, "Opened file is never closed; potential resource leak", 224360784Sdim ErrNode); 225280031Sdim R->markInteresting(LeakedStream); 226288943Sdim C.emitReport(std::move(R)); 227243791Sdim } 228243791Sdim} 229243791Sdim 230243791Sdimbool SimpleStreamChecker::guaranteedNotToCloseFile(const CallEvent &Call) const{ 231243791Sdim // If it's not in a system header, assume it might close a file. 232243791Sdim if (!Call.isInSystemHeader()) 233243791Sdim return false; 234243791Sdim 235243791Sdim // Handle cases where we know a buffer's /address/ can escape. 236243791Sdim if (Call.argumentsMayEscape()) 237243791Sdim return false; 238243791Sdim 239243791Sdim // Note, even though fclose closes the file, we do not list it here 240243791Sdim // since the checker is modeling the call. 241243791Sdim 242243791Sdim return true; 243243791Sdim} 244243791Sdim 245249423Sdim// If the pointer we are tracking escaped, do not track the symbol as 246243791Sdim// we cannot reason about it anymore. 247243791SdimProgramStateRef 248249423SdimSimpleStreamChecker::checkPointerEscape(ProgramStateRef State, 249249423Sdim const InvalidatedSymbols &Escaped, 250249423Sdim const CallEvent *Call, 251249423Sdim PointerEscapeKind Kind) const { 252249423Sdim // If we know that the call cannot close a file, there is nothing to do. 253261991Sdim if (Kind == PSK_DirectEscapeOnCall && guaranteedNotToCloseFile(*Call)) { 254243791Sdim return State; 255243791Sdim } 256243791Sdim 257249423Sdim for (InvalidatedSymbols::const_iterator I = Escaped.begin(), 258249423Sdim E = Escaped.end(); 259249423Sdim I != E; ++I) { 260249423Sdim SymbolRef Sym = *I; 261249423Sdim 262243791Sdim // The symbol escaped. Optimistically, assume that the corresponding file 263243791Sdim // handle will be closed somewhere else. 264249423Sdim State = State->remove<StreamMap>(Sym); 265243791Sdim } 266243791Sdim return State; 267243791Sdim} 268243791Sdim 269243791Sdimvoid ento::registerSimpleStreamChecker(CheckerManager &mgr) { 270243791Sdim mgr.registerChecker<SimpleStreamChecker>(); 271243791Sdim} 272353358Sdim 273353358Sdim// This checker should be enabled regardless of how language options are set. 274353358Sdimbool ento::shouldRegisterSimpleStreamChecker(const LangOptions &LO) { 275353358Sdim return true; 276353358Sdim} 277