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
