https://github.com/AZero13 created 
https://github.com/llvm/llvm-project/pull/206359

This PR is a year late, yes, but this came from my noticing this bug in 
libarchive: https://github.com/libarchive/libarchive/pull/2672

Basically, for write, 0 may not mean an error at all. We need to instead check 
for the length not being the same.

With fwrite, because 0 could mean an error, but not always. We must check that 
we wrote the entire file!

Note that unlike write, fwrite's description according to POSIX does not 
mention returning a negative type at all. Nor does it say you can retry unlike 
write.

Sometimes people get this mixed up, so I wrote a clang-tidy.

>From e5b8c0c57b7133f035b937a9300877078868ffe0 Mon Sep 17 00:00:00 2001
From: AZero13 <[email protected]>
Date: Sun, 28 Jun 2026 14:18:47 -0400
Subject: [PATCH] [clang-tidy] Add 'bugprone-suspicious-fread-fwrite-return'
 check

This PR is a year late, yes, but this came from my noticing this bug in 
libarchive: https://github.com/libarchive/libarchive/pull/2672

Basically, for write, 0 may not mean an error at all. We need to instead check 
for the length not being the same.

With fwrite, because 0 could mean an error, but not always. We must check that 
we wrote the entire file!

Note that unlike write, fwrite's description according to POSIX does not 
mention returning a negative type at all. Nor does it say you can retry unlike 
write.

Sometimes people get this mixed up, so I wrote a clang-tidy.
---
 .../bugprone/BugproneTidyModule.cpp           |   3 +
 .../clang-tidy/bugprone/CMakeLists.txt        |   1 +
 .../SuspiciousFreadFwriteReturnCheck.cpp      | 205 ++++++++++++++++++
 .../SuspiciousFreadFwriteReturnCheck.h        |  37 ++++
 clang-tools-extra/docs/ReleaseNotes.rst       |   9 +
 .../suspicious-fread-fwrite-return.rst        |  41 ++++
 .../docs/clang-tidy/checks/list.rst           |   1 +
 .../suspicious-fread-fwrite-return.cpp        |  95 ++++++++
 8 files changed, 392 insertions(+)
 create mode 100644 
clang-tools-extra/clang-tidy/bugprone/SuspiciousFreadFwriteReturnCheck.cpp
 create mode 100644 
clang-tools-extra/clang-tidy/bugprone/SuspiciousFreadFwriteReturnCheck.h
 create mode 100644 
clang-tools-extra/docs/clang-tidy/checks/bugprone/suspicious-fread-fwrite-return.rst
 create mode 100644 
clang-tools-extra/test/clang-tidy/checkers/bugprone/suspicious-fread-fwrite-return.cpp

diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp 
b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
index 3aa39d10ceb5d..84280d8872e1d 100644
--- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
@@ -86,6 +86,7 @@
 #include "StringLiteralWithEmbeddedNulCheck.h"
 #include "StringviewNullptrCheck.h"
 #include "SuspiciousEnumUsageCheck.h"
+#include "SuspiciousFreadFwriteReturnCheck.h"
 #include "SuspiciousIncludeCheck.h"
 #include "SuspiciousMemoryComparisonCheck.h"
 #include "SuspiciousMemsetUsageCheck.h"
@@ -184,6 +185,8 @@ class BugproneModule : public ClangTidyModule {
         "bugprone-incorrect-enable-if");
     CheckFactories.registerCheck<IncorrectEnableSharedFromThisCheck>(
         "bugprone-incorrect-enable-shared-from-this");
+    CheckFactories.registerCheck<SuspiciousFreadFwriteReturnCheck>(
+        "bugprone-suspicious-fread-fwrite-return");
     CheckFactories.registerCheck<UnintendedCharOstreamOutputCheck>(
         "bugprone-unintended-char-ostream-output");
     CheckFactories.registerCheck<ReturnConstRefFromParameterCheck>(
diff --git a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt 
b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
index 43e85b1407f21..387261799aff0 100644
--- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
@@ -39,6 +39,7 @@ add_clang_library(clangTidyBugproneModule STATIC
   IncorrectEnableSharedFromThisCheck.cpp
   InvalidEnumDefaultInitializationCheck.cpp
   MissingEndComparisonCheck.cpp
+  SuspiciousFreadFwriteReturnCheck.cpp
   UnintendedCharOstreamOutputCheck.cpp
   ReturnConstRefFromParameterCheck.cpp
   StdExceptionBaseclassCheck.cpp
diff --git 
a/clang-tools-extra/clang-tidy/bugprone/SuspiciousFreadFwriteReturnCheck.cpp 
b/clang-tools-extra/clang-tidy/bugprone/SuspiciousFreadFwriteReturnCheck.cpp
new file mode 100644
index 0000000000000..4259bf4c30a39
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/SuspiciousFreadFwriteReturnCheck.cpp
@@ -0,0 +1,205 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "SuspiciousFreadFwriteReturnCheck.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Lex/Lexer.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::bugprone {
+
+void SuspiciousFreadFwriteReturnCheck::registerMatchers(MatchFinder *Finder) {
+  auto FreadFwriteDecl = functionDecl(hasAnyName("::fread", "::fwrite"));
+  auto FreadFwriteCall =
+      callExpr(callee(FreadFwriteDecl), hasArgument(2, expr().bind("nmemb")))
+          .bind("call");
+
+  // 1. Direct comparison or Variable Initialization
+  auto CallOrVarInit =
+      anyOf(FreadFwriteCall, declRefExpr(to(varDecl(hasInitializer(
+                                 ignoringParenImpCasts(FreadFwriteCall))))));
+
+  auto CallOrVarInitExpr = ignoringParenImpCasts(CallOrVarInit);
+
+  // We only match IntegerLiteral (and unary '-' applied to IntegerLiteral) to
+  // avoid firing the matcher on complex expressions that EvaluateAsInt would
+  // later reject anyway.
+  auto OtherOperand = ignoringParenImpCasts(
+      expr(anyOf(integerLiteral(),
+                 unaryOperator(hasOperatorName("-"),
+                               hasUnaryOperand(integerLiteral()))))
+          .bind("other"));
+
+  auto CompLHS =
+      binaryOperator(hasAnyOperatorName("==", "!=", "<", "<=", ">", ">="),
+                     hasLHS(CallOrVarInitExpr), hasRHS(OtherOperand))
+          .bind("suspicious_lhs");
+
+  auto CompRHS =
+      binaryOperator(hasAnyOperatorName("==", "!=", "<", "<=", ">", ">="),
+                     hasLHS(OtherOperand), hasRHS(CallOrVarInitExpr))
+          .bind("suspicious_rhs");
+
+  Finder->addMatcher(CompLHS, this);
+  Finder->addMatcher(CompRHS, this);
+}
+
+void SuspiciousFreadFwriteReturnCheck::check(
+    const MatchFinder::MatchResult &Result) {
+  const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call");
+  if (!Call || !Call->getDirectCallee())
+    return;
+
+  StringRef FuncName = Call->getDirectCallee()->getName();
+
+  const BinaryOperator *BinOp = nullptr;
+  bool IsCallLHS = false;
+
+  if (const auto *Op =
+          Result.Nodes.getNodeAs<BinaryOperator>("suspicious_lhs")) {
+    BinOp = Op;
+    IsCallLHS = true;
+  } else if (const auto *Op =
+                 Result.Nodes.getNodeAs<BinaryOperator>("suspicious_rhs")) {
+    BinOp = Op;
+    IsCallLHS = false;
+  }
+
+  if (!BinOp)
+    return;
+
+  const auto *OtherExpr = Result.Nodes.getNodeAs<Expr>("other");
+  if (!OtherExpr)
+    return;
+
+  Expr::EvalResult Eval;
+  if (!OtherExpr->EvaluateAsInt(Eval, *Result.Context) || Eval.HasSideEffects)
+    return;
+
+  llvm::APSInt Val = Eval.Val.getInt();
+  BinaryOperatorKind Op = BinOp->getOpcode();
+
+  const Expr *CallOperand = IsCallLHS ? BinOp->getLHS() : BinOp->getRHS();
+  bool IsDirectComparison = isa<CallExpr>(CallOperand->IgnoreParenImpCasts());
+
+  const auto *NmembExpr = Result.Nodes.getNodeAs<Expr>("nmemb");
+  assert(NmembExpr && "nmemb should always be bound");
+
+  bool IsNmembOne = false;
+  Expr::EvalResult NmembEval;
+  if (NmembExpr->EvaluateAsInt(NmembEval, *Result.Context) &&
+      !NmembEval.HasSideEffects) {
+    if (NmembEval.Val.getInt().isOne())
+      IsNmembOne = true;
+  }
+
+  enum class DiagnosticKind {
+    None,
+    AlwaysFalse,
+    AlwaysTrue,
+    Suspicious,
+    Discarded
+  };
+  DiagnosticKind DiagKind = DiagnosticKind::None;
+
+  if (Val.isNegative()) {
+    if (Op == BO_EQ)
+      DiagKind = DiagnosticKind::AlwaysFalse;
+    else if (Op == BO_NE)
+      DiagKind = DiagnosticKind::AlwaysTrue;
+    else if (IsCallLHS) {
+      if (Op == BO_LT || Op == BO_LE)
+        DiagKind = DiagnosticKind::AlwaysFalse;
+      else if (Op == BO_GT || Op == BO_GE)
+        DiagKind = DiagnosticKind::AlwaysTrue;
+    } else {
+      if (Op == BO_GT || Op == BO_GE)
+        DiagKind = DiagnosticKind::AlwaysFalse;
+      else if (Op == BO_LT || Op == BO_LE)
+        DiagKind = DiagnosticKind::AlwaysTrue;
+    }
+  } else if (Val.isZero()) {
+    if (IsCallLHS) {
+      if (Op == BO_LT)
+        DiagKind = DiagnosticKind::AlwaysFalse;
+      else if (Op == BO_GE)
+        DiagKind = DiagnosticKind::AlwaysTrue;
+      else if (Op == BO_LE)
+        DiagKind = DiagnosticKind::Suspicious;
+      else if (IsDirectComparison && !IsNmembOne &&
+               (Op == BO_EQ || Op == BO_NE || Op == BO_GT))
+        DiagKind = DiagnosticKind::Discarded;
+    } else {
+      if (Op == BO_GT)
+        DiagKind = DiagnosticKind::AlwaysFalse;
+      else if (Op == BO_LE)
+        DiagKind = DiagnosticKind::AlwaysTrue;
+      else if (Op == BO_GE)
+        DiagKind = DiagnosticKind::Suspicious;
+      else if (IsDirectComparison && !IsNmembOne &&
+               (Op == BO_EQ || Op == BO_NE || Op == BO_LT))
+        DiagKind = DiagnosticKind::Discarded;
+    }
+  }
+
+  if (DiagKind == DiagnosticKind::None)
+    return;
+
+  std::optional<FixItHint> Hint;
+  StringRef NmembStr = Lexer::getSourceText(
+      CharSourceRange::getTokenRange(NmembExpr->getSourceRange()),
+      *Result.SourceManager, Result.Context->getLangOpts());
+  if (!NmembStr.empty()) {
+    if (IsCallLHS) {
+      Hint = FixItHint::CreateReplacement(
+          SourceRange(BinOp->getOperatorLoc(), OtherExpr->getEndLoc()),
+          ("!= " + NmembStr).str());
+    } else {
+      Hint = FixItHint::CreateReplacement(
+          SourceRange(OtherExpr->getBeginLoc(), BinOp->getOperatorLoc()),
+          (NmembStr.str() + " !="));
+    }
+  }
+
+  if (DiagKind == DiagnosticKind::AlwaysFalse) {
+    auto D = diag(BinOp->getOperatorLoc(),
+                  "return value of '%0' is an unsigned 'size_t'; this "
+                  "comparison is always false");
+    D << FuncName;
+    if (Hint)
+      D << *Hint;
+  } else if (DiagKind == DiagnosticKind::AlwaysTrue) {
+    auto D = diag(BinOp->getOperatorLoc(),
+                  "return value of '%0' is an unsigned 'size_t'; this "
+                  "comparison is always true");
+    D << FuncName;
+    if (Hint)
+      D << *Hint;
+  } else if (DiagKind == DiagnosticKind::Suspicious) {
+    StringRef BadExpr = Op == BO_LE ? "<= 0" : "0 >=";
+    auto D = diag(BinOp->getOperatorLoc(),
+                  "suspicious comparison against 0; '%0' returns an unsigned "
+                  "'size_t', so comparing it with '%1' is equivalent to "
+                  "comparing it with '== 0'. To detect short reads or writes, "
+                  "compare against the 'nmemb' argument");
+    D << FuncName << BadExpr;
+    if (Hint)
+      D << *Hint;
+  } else if (DiagKind == DiagnosticKind::Discarded) {
+    auto D = diag(BinOp->getOperatorLoc(),
+                  "return value of '%0' is compared to 0; since 'nmemb' is not 
"
+                  "1, partial reads or writes cannot be handled. Compare "
+                  "against the 'nmemb' argument instead");
+    D << FuncName;
+    if (Hint)
+      D << *Hint;
+  }
+}
+
+} // namespace clang::tidy::bugprone
diff --git 
a/clang-tools-extra/clang-tidy/bugprone/SuspiciousFreadFwriteReturnCheck.h 
b/clang-tools-extra/clang-tidy/bugprone/SuspiciousFreadFwriteReturnCheck.h
new file mode 100644
index 0000000000000..3530a77a916fc
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/SuspiciousFreadFwriteReturnCheck.h
@@ -0,0 +1,37 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef 
LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SUSPICIOUSFREADFWRITERETURNCHECK_H
+#define 
LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SUSPICIOUSFREADFWRITERETURNCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::bugprone {
+
+/// Finds suspicious checks of the return value of `fread` and `fwrite`.
+///
+/// Developers sometimes mistakenly treat the result like the `ssize_t`
+/// return value of POSIX `read` and `write`. Unlike those functions,
+/// `fread` and `fwrite` return the number of elements transferred as a
+/// `size_t`. When more than one element is requested, comparing the result
+/// against zero does not detect partial reads or writes. Correct code should
+/// compare the returned element count against the requested `nmemb`.
+///
+/// For the user-facing documentation see:
+/// 
https://clang.llvm.org/extra/clang-tidy/checks/bugprone/suspicious-fread-fwrite-return.html
+class SuspiciousFreadFwriteReturnCheck : public ClangTidyCheck {
+public:
+  SuspiciousFreadFwriteReturnCheck(StringRef Name, ClangTidyContext *Context)
+      : ClangTidyCheck(Name, Context) {}
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+};
+
+} // namespace clang::tidy::bugprone
+
+#endif // 
LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_SUSPICIOUSFREADFWRITERETURNCHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst 
b/clang-tools-extra/docs/ReleaseNotes.rst
index 81e5de4e0a868..5436d859ddffc 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -225,6 +225,15 @@ New checks
   Finds instances where the result of a standard algorithm is used in a Boolean
   context without being compared to the end iterator.
 
+- New :doc:`bugprone-suspicious-fread-fwrite-return
+  <clang-tidy/checks/bugprone/suspicious-fread-fwrite-return>` check.
+
+  Finds suspicious checks of the return value of ``fread`` and ``fwrite``.
+  Developers sometimes mistakenly treat the result like the ``ssize_t``
+  return value of POSIX ``read`` and ``write``. ``fread`` and ``fwrite`` 
instead
+  return the number of elements transferred as a ``size_t``, making tests
+  such as ``<= 0`` incorrect.
+
 - New :doc:`bugprone-unsafe-to-allow-exceptions
   <clang-tidy/checks/bugprone/unsafe-to-allow-exceptions>` check.
 
diff --git 
a/clang-tools-extra/docs/clang-tidy/checks/bugprone/suspicious-fread-fwrite-return.rst
 
b/clang-tools-extra/docs/clang-tidy/checks/bugprone/suspicious-fread-fwrite-return.rst
new file mode 100644
index 0000000000000..b3c9e43e521a3
--- /dev/null
+++ 
b/clang-tools-extra/docs/clang-tidy/checks/bugprone/suspicious-fread-fwrite-return.rst
@@ -0,0 +1,41 @@
+.. title:: clang-tidy - bugprone-suspicious-fread-fwrite-return
+
+bugprone-suspicious-fread-fwrite-return
+=======================================
+
+Finds suspicious checks of the return value of ``fread`` and ``fwrite``.
+
+Developers sometimes mistakenly treat the result like the ``ssize_t``
+return value of POSIX ``read`` and ``write``. Unlike those functions,
+``fread`` and ``fwrite`` return the number of elements transferred as a
+``size_t``. When more than one element is requested, comparing the result
+against zero does not detect partial reads or writes. Correct code should 
compare the returned element
+count against the requested ``nmemb``.
+
+Examples
+--------
+
+.. code-block:: c++
+
+    size_t length = 100;
+    
+    // Incorrect: Does not check for short writes.
+    if (fwrite(buf, 1, length, fp) <= 0) {
+        // ...
+    }
+
+    // Incorrect: Discards the short read count.
+    if (fread(buf, 1, length, fp) == 0) {
+        // ...
+    }
+
+    // Incorrect: Tautological condition.
+    size_t written = fwrite(buf, 1, length, fp);
+    if (written < 0) {
+        // ...
+    }
+
+    // Correct: Compare against the requested number of items.
+    if (fwrite(buf, 1, length, fp) != length) {
+        // ...
+    }
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst 
b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index 0eb9e8a243081..f86452c582eee 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -155,6 +155,7 @@ Clang-Tidy Checks
    :doc:`bugprone-string-literal-with-embedded-nul 
<bugprone/string-literal-with-embedded-nul>`,
    :doc:`bugprone-stringview-nullptr <bugprone/stringview-nullptr>`, "Yes"
    :doc:`bugprone-suspicious-enum-usage <bugprone/suspicious-enum-usage>`,
+   :doc:`bugprone-suspicious-fread-fwrite-return 
<bugprone/suspicious-fread-fwrite-return>`, "Yes"
    :doc:`bugprone-suspicious-include <bugprone/suspicious-include>`,
    :doc:`bugprone-suspicious-memory-comparison 
<bugprone/suspicious-memory-comparison>`,
    :doc:`bugprone-suspicious-memset-usage <bugprone/suspicious-memset-usage>`, 
"Yes"
diff --git 
a/clang-tools-extra/test/clang-tidy/checkers/bugprone/suspicious-fread-fwrite-return.cpp
 
b/clang-tools-extra/test/clang-tidy/checkers/bugprone/suspicious-fread-fwrite-return.cpp
new file mode 100644
index 0000000000000..972ddd6463efc
--- /dev/null
+++ 
b/clang-tools-extra/test/clang-tidy/checkers/bugprone/suspicious-fread-fwrite-return.cpp
@@ -0,0 +1,95 @@
+// RUN: %check_clang_tidy %s bugprone-suspicious-fread-fwrite-return %t
+
+typedef decltype(sizeof(int)) size_t;
+
+struct FILE;
+extern "C" size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
+extern "C" size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE 
*stream);
+
+void test_direct_comparison(FILE *fp, void *buf, size_t size) {
+  if (fwrite(buf, 1, size, fp) < -1) {
+    // CHECK-MESSAGES: :[[@LINE-1]]:32: warning: return value of 'fwrite' is 
an unsigned 'size_t'; this comparison is always false 
[bugprone-suspicious-fread-fwrite-return]
+    // CHECK-FIXES: if (fwrite(buf, 1, size, fp) != size) {
+  }
+
+  if (fwrite(buf, 1, size, fp) >= -1) {
+    // CHECK-MESSAGES: :[[@LINE-1]]:32: warning: return value of 'fwrite' is 
an unsigned 'size_t'; this comparison is always true
+    // CHECK-FIXES: if (fwrite(buf, 1, size, fp) != size) {
+  }
+
+  if (fwrite(buf, 1, size, fp) <= 0) {
+    // CHECK-MESSAGES: :[[@LINE-1]]:32: warning: suspicious comparison against 
0; 'fwrite' returns an unsigned 'size_t', so comparing it with '<= 0' is 
equivalent to comparing it with '== 0'. To detect short reads or writes, 
compare against the 'nmemb' argument
+    // CHECK-FIXES: if (fwrite(buf, 1, size, fp) != size) {
+  }
+
+  if (0 >= fread(buf, 1, size, fp)) {
+    // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: suspicious comparison against 
0; 'fread' returns an unsigned 'size_t', so comparing it with '0 >=' is 
equivalent to comparing it with '== 0'. To detect short reads or writes, 
compare against the 'nmemb' argument
+    // CHECK-FIXES: if (size != fread(buf, 1, size, fp)) {
+  }
+
+  if (0 < fread(buf, 1, size, fp)) {
+    // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: return value of 'fread' is an 
unsigned 'size_t'; this comparison is always true
+    // CHECK-FIXES: if (size != fread(buf, 1, size, fp)) {
+  }
+}
+
+void test_discarded_result(FILE *fp, void *buf, size_t size) {
+  if (fread(buf, 1, size, fp) == 0) {
+    // CHECK-MESSAGES: :[[@LINE-1]]:31: warning: return value of 'fread' is 
compared to 0; since 'nmemb' is not 1, partial reads or writes cannot be 
handled. Compare against the 'nmemb' argument instead
+    // CHECK-FIXES: if (fread(buf, 1, size, fp) != size) {
+  }
+
+  if (fread(buf, 1, size, fp) != 0) {
+    // CHECK-MESSAGES: :[[@LINE-1]]:31: warning: return value of 'fread' is 
compared to 0; since 'nmemb' is not 1, partial reads or writes cannot be 
handled. Compare against the 'nmemb' argument instead
+    // CHECK-FIXES: if (fread(buf, 1, size, fp) != size) {
+  }
+
+  if (fread(buf, 1, size, fp) > 0) {
+    // CHECK-MESSAGES: :[[@LINE-1]]:31: warning: return value of 'fread' is 
compared to 0; since 'nmemb' is not 1, partial reads or writes cannot be 
handled. Compare against the 'nmemb' argument instead
+    // CHECK-FIXES: if (fread(buf, 1, size, fp) != size) {
+  }
+
+  // If nmemb is exactly 1, == 0 is safe because it's an all-or-nothing read
+  if (fread(buf, size, 1, fp) == 0) {
+    // No warning
+  }
+  
+  // Streaming/EOF checks (like while(> 0)) are valid if not a direct 
comparison (e.g., they assign or loop).
+  // However, this check only catches direct comparisons to avoid breaking 
legitimate streaming loops.
+  while (fwrite(buf, 1, size, fp) > 0) {
+    // CHECK-MESSAGES: :[[@LINE-1]]:35: warning: return value of 'fwrite' is 
compared to 0; since 'nmemb' is not 1, partial reads or writes cannot be 
handled. Compare against the 'nmemb' argument instead
+    // CHECK-FIXES: while (fwrite(buf, 1, size, fp) != size) {
+  }
+}
+
+void test_correct_handling(FILE *fp, void *buf, size_t size) {
+  if (fwrite(buf, 1, size, fp) != size) {
+    // No warning
+  }
+  
+  if (fwrite(buf, 1, size, fp) < size) {
+    // No warning
+  }
+
+  size_t written = fwrite(buf, 1, size, fp);
+  if (written < size) {
+    // No warning
+  }
+  
+  if (written <= 0) {
+    // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: suspicious comparison against 
0; 'fwrite' returns an unsigned 'size_t', so comparing it with '<= 0' is 
equivalent to comparing it with '== 0'. To detect short reads or writes, 
compare against the 'nmemb' argument
+    // CHECK-FIXES: if (written != size) {
+  }
+}
+
+#define SIZE 16
+
+void test_macro_handling(FILE *fp, void *buf) {
+  if (fwrite(buf, 1, SIZE, fp) <= 0) {
+    // CHECK-MESSAGES: :[[@LINE-1]]:32: warning: suspicious comparison against 
0; 'fwrite' returns an unsigned 'size_t', so comparing it with '<= 0' is 
equivalent to comparing it with '== 0'. To detect short reads or writes, 
compare against the 'nmemb' argument
+    // CHECK-FIXES: if (fwrite(buf, 1, SIZE, fp) != SIZE) {
+  }
+}
+
+
+

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to