[clang-tools-extra] [clang-tidy] Add 'bugprone-suspicious-fread-fwrite-return' check (PR #206359)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Jun 28 12:08:17 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-tidy
Author: AZero13 (AZero13)
<details>
<summary>Changes</summary>
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.
---
Full diff: https://github.com/llvm/llvm-project/pull/206359.diff
8 Files Affected:
- (modified) clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp (+3)
- (modified) clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt (+1)
- (added) clang-tools-extra/clang-tidy/bugprone/SuspiciousFreadFwriteReturnCheck.cpp (+205)
- (added) clang-tools-extra/clang-tidy/bugprone/SuspiciousFreadFwriteReturnCheck.h (+37)
- (modified) clang-tools-extra/docs/ReleaseNotes.rst (+9)
- (added) clang-tools-extra/docs/clang-tidy/checks/bugprone/suspicious-fread-fwrite-return.rst (+41)
- (modified) clang-tools-extra/docs/clang-tidy/checks/list.rst (+1)
- (added) clang-tools-extra/test/clang-tidy/checkers/bugprone/suspicious-fread-fwrite-return.cpp (+95)
``````````diff
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) {
+ }
+}
+
+
+
``````````
</details>
https://github.com/llvm/llvm-project/pull/206359
More information about the cfe-commits
mailing list