[clang] [clang][analyzer] Add 'tmpfile' as an open function to SimpleStreamChecker (PR #70539)

Ben Shi via cfe-commits cfe-commits at lists.llvm.org
Fri Oct 27 23:45:06 PDT 2023


https://github.com/benshi001 created https://github.com/llvm/llvm-project/pull/70539

None

>From 10bd2b51df19b6a2a76ad326462528d7aebfc548 Mon Sep 17 00:00:00 2001
From: Ben Shi <bennshi at tencent.com>
Date: Sat, 28 Oct 2023 14:41:47 +0800
Subject: [PATCH] [clang][analyzer] Add 'tmpfile' as an open function to
 SimpleStreamChecker

---
 .../Checkers/SimpleStreamChecker.cpp          | 64 ++++++++++---------
 ...ystem-header-simulator-for-simple-stream.h |  1 +
 clang/test/Analysis/simple-stream-checks.c    | 24 +++++++
 3 files changed, 58 insertions(+), 31 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp
index 32d95e944195390..178ebd00ac6377c 100644
--- a/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp
@@ -1,4 +1,4 @@
-//===-- SimpleStreamChecker.cpp -----------------------------------------*- C++ -*--//
+//===-- SimpleStreamChecker.cpp -----------------------------------*- C++ -*--//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.
@@ -20,6 +20,7 @@
 #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include <array>
 #include <utility>
 
 using namespace clang;
@@ -31,7 +32,7 @@ typedef SmallVector<SymbolRef, 2> SymbolVector;
 struct StreamState {
 private:
   enum Kind { Opened, Closed } K;
-  StreamState(Kind InK) : K(InK) { }
+  StreamState(Kind InK) : K(InK) {}
 
 public:
   bool isOpened() const { return K == Opened; }
@@ -40,25 +41,20 @@ struct StreamState {
   static StreamState getOpened() { return StreamState(Opened); }
   static StreamState getClosed() { return StreamState(Closed); }
 
-  bool operator==(const StreamState &X) const {
-    return K == X.K;
-  }
-  void Profile(llvm::FoldingSetNodeID &ID) const {
-    ID.AddInteger(K);
-  }
+  bool operator==(const StreamState &X) const { return K == X.K; }
+  void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); }
 };
 
-class SimpleStreamChecker : public Checker<check::PostCall,
-                                           check::PreCall,
-                                           check::DeadSymbols,
-                                           check::PointerEscape> {
-  CallDescription OpenFn, CloseFn;
+class SimpleStreamChecker
+    : public Checker<check::PostCall, check::PreCall, check::DeadSymbols,
+                     check::PointerEscape> {
+  std::array<CallDescription, 2> OpenFns;
+  CallDescription CloseFn;
 
   std::unique_ptr<BugType> DoubleCloseBugType;
   std::unique_ptr<BugType> LeakBugType;
 
-  void reportDoubleClose(SymbolRef FileDescSym,
-                         const CallEvent &Call,
+  void reportDoubleClose(SymbolRef FileDescSym, const CallEvent &Call,
                          CheckerContext &C) const;
 
   void reportLeaks(ArrayRef<SymbolRef> LeakedStreams, CheckerContext &C,
@@ -78,9 +74,9 @@ class SimpleStreamChecker : public Checker<check::PostCall,
 
   /// Stop tracking addresses which escape.
   ProgramStateRef checkPointerEscape(ProgramStateRef State,
-                                    const InvalidatedSymbols &Escaped,
-                                    const CallEvent *Call,
-                                    PointerEscapeKind Kind) const;
+                                     const InvalidatedSymbols &Escaped,
+                                     const CallEvent *Call,
+                                     PointerEscapeKind Kind) const;
 };
 
 } // end anonymous namespace
@@ -90,15 +86,15 @@ class SimpleStreamChecker : public Checker<check::PostCall,
 REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
 
 SimpleStreamChecker::SimpleStreamChecker()
-    : OpenFn({"fopen"}), CloseFn({"fclose"}, 1) {
+    : OpenFns({CallDescription({"fopen"}), CallDescription({"tmpfile"}, 0)}),
+      CloseFn({"fclose"}, 1) {
   // Initialize the bug types.
   DoubleCloseBugType.reset(
       new BugType(this, "Double fclose", "Unix Stream API Error"));
 
   // Sinks are higher importance bugs as well as calls to assert() or exit(0).
-  LeakBugType.reset(
-      new BugType(this, "Resource Leak", "Unix Stream API Error",
-                  /*SuppressOnSink=*/true));
+  LeakBugType.reset(new BugType(this, "Resource Leak", "Unix Stream API Error",
+                                /*SuppressOnSink=*/true));
 }
 
 void SimpleStreamChecker::checkPostCall(const CallEvent &Call,
@@ -106,7 +102,14 @@ void SimpleStreamChecker::checkPostCall(const CallEvent &Call,
   if (!Call.isGlobalCFunction())
     return;
 
-  if (!OpenFn.matches(Call))
+  // Search for a matched open function.
+  bool Opened = false;
+  for (auto &Fn : OpenFns)
+    if (Fn.matches(Call)) {
+      Opened = true;
+      break;
+    }
+  if (!Opened)
     return;
 
   // Get the symbolic value corresponding to the file handle.
@@ -146,8 +149,8 @@ void SimpleStreamChecker::checkPreCall(const CallEvent &Call,
   C.addTransition(State);
 }
 
-static bool isLeaked(SymbolRef Sym, const StreamState &SS,
-                     bool IsSymDead, ProgramStateRef State) {
+static bool isLeaked(SymbolRef Sym, const StreamState &SS, bool IsSymDead,
+                     ProgramStateRef State) {
   if (IsSymDead && SS.isOpened()) {
     // If a symbol is NULL, assume that fopen failed on this path.
     // A symbol should only be considered leaked if it is non-null.
@@ -212,7 +215,8 @@ void SimpleStreamChecker::reportLeaks(ArrayRef<SymbolRef> LeakedStreams,
   }
 }
 
-bool SimpleStreamChecker::guaranteedNotToCloseFile(const CallEvent &Call) const{
+bool SimpleStreamChecker::guaranteedNotToCloseFile(
+    const CallEvent &Call) const {
   // If it's not in a system header, assume it might close a file.
   if (!Call.isInSystemHeader())
     return false;
@@ -229,11 +233,9 @@ bool SimpleStreamChecker::guaranteedNotToCloseFile(const CallEvent &Call) const{
 
 // If the pointer we are tracking escaped, do not track the symbol as
 // we cannot reason about it anymore.
-ProgramStateRef
-SimpleStreamChecker::checkPointerEscape(ProgramStateRef State,
-                                        const InvalidatedSymbols &Escaped,
-                                        const CallEvent *Call,
-                                        PointerEscapeKind Kind) const {
+ProgramStateRef SimpleStreamChecker::checkPointerEscape(
+    ProgramStateRef State, const InvalidatedSymbols &Escaped,
+    const CallEvent *Call, PointerEscapeKind Kind) const {
   // If we know that the call cannot close a file, there is nothing to do.
   if (Kind == PSK_DirectEscapeOnCall && guaranteedNotToCloseFile(*Call)) {
     return State;
diff --git a/clang/test/Analysis/Inputs/system-header-simulator-for-simple-stream.h b/clang/test/Analysis/Inputs/system-header-simulator-for-simple-stream.h
index b65b7a6b0e7b020..194a8a269e722ee 100644
--- a/clang/test/Analysis/Inputs/system-header-simulator-for-simple-stream.h
+++ b/clang/test/Analysis/Inputs/system-header-simulator-for-simple-stream.h
@@ -13,6 +13,7 @@ int fputc(int, FILE *);
 int fputs(const char * restrict, FILE * restrict) __asm("_" "fputs" );
 int fclose(FILE *);
 void exit(int);
+FILE *tmpfile(void);
 
 // The following is a fake system header function
 typedef struct __FileStruct {
diff --git a/clang/test/Analysis/simple-stream-checks.c b/clang/test/Analysis/simple-stream-checks.c
index 767ffaf53fcbfd1..705dde84b893fed 100644
--- a/clang/test/Analysis/simple-stream-checks.c
+++ b/clang/test/Analysis/simple-stream-checks.c
@@ -94,3 +94,27 @@ void testOverwrite(void) {
   FILE *fp = fopen("myfile.txt", "w");
   fp = 0;
 } // expected-warning {{Opened file is never closed; potential resource leak}}
+
+void testTmpfileNotClose(void) {
+  FILE *fp = tmpfile();
+  fputs("abc", fp);
+  return; // expected-warning {{Opened file is never closed; potential resource leak}}
+}
+
+void testTmpfileClose(void) {
+  FILE *fp = tmpfile();
+  fclose(fp);
+  return; // no warning
+}
+
+void testTmpfileEscaped(void) {
+  FILE *fp = tmpfile();
+  myfclose(fp);
+  return; // no warning
+}
+
+void testTmpfileNotEscaped(void) {
+  FILE *fp = tmpfile();
+  passConstPointer(fp);
+  return; // expected-warning {{Opened file is never closed; potential resource leak}}
+}



More information about the cfe-commits mailing list