[clang-tools-extra] 3e12b2e - [clang-tidy] Fix modernize-use-std-print check when return value used

Piotr Zegar via cfe-commits cfe-commits at lists.llvm.org
Tue Jun 27 10:24:14 PDT 2023


Author: Mike Crowe
Date: 2023-06-27T17:23:52Z
New Revision: 3e12b2e207cfa802937488a2c0b90d482eaf00a9

URL: https://github.com/llvm/llvm-project/commit/3e12b2e207cfa802937488a2c0b90d482eaf00a9
DIFF: https://github.com/llvm/llvm-project/commit/3e12b2e207cfa802937488a2c0b90d482eaf00a9.diff

LOG: [clang-tidy] Fix modernize-use-std-print check when return value used

The initial implementation of the modernize-use-std-print check was
capable of converting calls to printf (etc.) which used the return value
to calls to std::print which has no return value, thus breaking the
code.

Use code inspired by the implementation of bugprone-unused-return-value
check to ignore cases where the return value is used. Add appropriate
lit test cases and documentation.

Reviewed By: PiotrZSL

Differential Revision: https://reviews.llvm.org/D153860

Added: 
    

Modified: 
    clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp
    clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-print.rst
    clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-absl.cpp
    clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-custom.cpp
    clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print.cpp

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp
index 9b368f9a3e6c9..b1e1189d4ed77 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp
@@ -70,27 +70,53 @@ void UseStdPrintCheck::registerPPCallbacks(const SourceManager &SM,
   IncludeInserter.registerPreprocessor(PP);
 }
 
+static clang::ast_matchers::StatementMatcher
+unusedReturnValue(clang::ast_matchers::StatementMatcher MatchedCallExpr) {
+  auto UnusedInCompoundStmt =
+      compoundStmt(forEach(MatchedCallExpr),
+                   // The checker can't currently 
diff erentiate between the
+                   // return statement and other statements inside GNU statement
+                   // expressions, so disable the checker inside them to avoid
+                   // false positives.
+                   unless(hasParent(stmtExpr())));
+  auto UnusedInIfStmt =
+      ifStmt(eachOf(hasThen(MatchedCallExpr), hasElse(MatchedCallExpr)));
+  auto UnusedInWhileStmt = whileStmt(hasBody(MatchedCallExpr));
+  auto UnusedInDoStmt = doStmt(hasBody(MatchedCallExpr));
+  auto UnusedInForStmt =
+      forStmt(eachOf(hasLoopInit(MatchedCallExpr),
+                     hasIncrement(MatchedCallExpr), hasBody(MatchedCallExpr)));
+  auto UnusedInRangeForStmt = cxxForRangeStmt(hasBody(MatchedCallExpr));
+  auto UnusedInCaseStmt = switchCase(forEach(MatchedCallExpr));
+
+  return stmt(anyOf(UnusedInCompoundStmt, UnusedInIfStmt, UnusedInWhileStmt,
+                    UnusedInDoStmt, UnusedInForStmt, UnusedInRangeForStmt,
+                    UnusedInCaseStmt));
+}
+
 void UseStdPrintCheck::registerMatchers(MatchFinder *Finder) {
   if (!PrintfLikeFunctions.empty())
     Finder->addMatcher(
-        callExpr(argumentCountAtLeast(1),
-                 hasArgument(0, stringLiteral(isOrdinary())),
-                 callee(functionDecl(
-                            unless(cxxMethodDecl()),
-                            matchers::matchesAnyListedName(PrintfLikeFunctions))
-                            .bind("func_decl")))
-            .bind("printf"),
+        unusedReturnValue(
+            callExpr(argumentCountAtLeast(1),
+                     hasArgument(0, stringLiteral(isOrdinary())),
+                     callee(functionDecl(unless(cxxMethodDecl()),
+                                         matchers::matchesAnyListedName(
+                                             PrintfLikeFunctions))
+                                .bind("func_decl")))
+                .bind("printf")),
         this);
 
   if (!FprintfLikeFunctions.empty())
     Finder->addMatcher(
-        callExpr(argumentCountAtLeast(2),
-                 hasArgument(1, stringLiteral(isOrdinary())),
-                 callee(functionDecl(unless(cxxMethodDecl()),
-                                     matchers::matchesAnyListedName(
-                                         FprintfLikeFunctions))
-                            .bind("func_decl")))
-            .bind("fprintf"),
+        unusedReturnValue(
+            callExpr(argumentCountAtLeast(2),
+                     hasArgument(1, stringLiteral(isOrdinary())),
+                     callee(functionDecl(unless(cxxMethodDecl()),
+                                         matchers::matchesAnyListedName(
+                                             FprintfLikeFunctions))
+                                .bind("func_decl")))
+                .bind("fprintf")),
         this);
 }
 

diff  --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-print.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-print.rst
index 385ee35c4f8f8..8034238bea90e 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-print.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-print.rst
@@ -49,6 +49,13 @@ It doesn't do a bad job, but it's not perfect. In particular:
 
   - The glibc extension ``%m``.
 
+- ``printf`` and similar functions return the number of characters printed.
+  ``std::print`` does not. This means that any invocations that use the
+  return value will not be converted. Unfortunately this currently includes
+  explicitly-casting to ``void``. Deficiencies in this check mean that any
+  invocations inside ``GCC`` compound statements cannot be converted even
+  if the resulting value is not used.
+
 If conversion would be incomplete or unsafe then the entire invocation will
 be left unchanged.
 

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-absl.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-absl.cpp
index 5345380028309..e35d79d1bef5e 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-absl.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-absl.cpp
@@ -55,6 +55,14 @@ void printf_no_casts_in_strict_mode() {
   // CHECK-FIXES: std::println("Unsigned integer {} from short", s);
 }
 
+int printf_uses_return_value(int i) {
+  using namespace absl;
+
+  return PrintF("return value %d\n", i);
+  // CHECK-MESSAGES-NOT: [[@LINE-1]]:10: warning: use 'std::println' instead of 'PrintF' [modernize-use-std-print]
+  // CHECK-FIXES-NOT: std::println("return value {}", i);
+}
+
 void fprintf_simple(FILE *fp) {
   absl::FPrintF(fp, "Hello %s %d", "world", 42);
   // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'FPrintF' [modernize-use-std-print]
@@ -85,3 +93,11 @@ void fprintf_no_casts_in_strict_mode(FILE *fp) {
   // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::println' instead of 'FPrintF' [modernize-use-std-print]
   // CHECK-FIXES: std::println(fp, "Unsigned integer {} from short", s);
 }
+
+int fprintf_uses_return_value(FILE *fp, int i) {
+  using namespace absl;
+
+  return FPrintF(fp, "return value %d\n", i);
+  // CHECK-MESSAGES-NOT: [[@LINE-1]]:10: warning: use 'std::println' instead of 'FPrintF' [modernize-use-std-print]
+  // CHECK-FIXES-NOT: std::println("return value {}", i);
+}

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-custom.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-custom.cpp
index 08779837c75e7..a8dda40651b7b 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-custom.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print-custom.cpp
@@ -48,6 +48,12 @@ void printf_newline() {
   printf("Hello %s %d\n", "world", 42);
 }
 
+int printf_uses_return_value(int i) {
+  return myprintf("return value %d\n", i);
+  // CHECK-MESSAGES-NOT: [[@LINE-1]]:10: warning: use 'std::println' instead of 'myprintf' [modernize-use-std-print]
+  // CHECK-FIXES-NOT: std::println("return value {}", i);
+}
+
 void fprintf_simple(FILE *fp)
 {
   myfprintf(stderr, "Hello %s %d", "world", 42);
@@ -73,3 +79,9 @@ void fprintf_newline(FILE *fp)
   // When using custom options leave fprintf alone
   fprintf(stderr, "Hello %s %d\n", "world", 42);
 }
+
+int fprintf_uses_return_value(int i) {
+  return myfprintf(stderr, "return value %d\n", i);
+  // CHECK-MESSAGES-NOT: [[@LINE-1]]:10: warning: use 'std::println' instead of 'myprintf' [modernize-use-std-print]
+  // CHECK-FIXES-NOT: std::println(stderr, "return value {}", i);
+}

diff  --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print.cpp
index a40eec922ab55..e75c2d4cc019f 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-print.cpp
@@ -44,6 +44,233 @@ void printf_deceptive_newline() {
   // CHECK-FIXES: std::println("Hello");
 }
 
+// std::print returns nothing, so any callers that use the return
+// value cannot be automatically translated.
+int printf_uses_return_value(int choice) {
+  const int i = printf("Return value assigned to variable %d\n", 42);
+
+  if (choice == 0)
+    printf("if body %d\n", i);
+    // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println("if body {}", i);
+  else if (choice == 1)
+    printf("else if body %d\n", i);
+    // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println("else if body {}", i);
+  else
+    printf("else body %d\n", i);
+    // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println("else body {}", i);
+
+  if (printf("Return value used as boolean in if statement"))
+    if (printf("Return value used in expression if statement") == 44)
+      if (const int j = printf("Return value used in assignment in if statement"))
+        if (const int k = printf("Return value used with initializer in if statement"); k == 44)
+          ;
+
+  int d = 0;
+  while (printf("%d", d) < 2)
+    ++d;
+
+  while (true)
+    printf("while body %d\n", i);
+    // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println("while body {}", i);
+
+  do
+    printf("do body %d\n", i);
+    // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println("do body {}", i);
+  while (true);
+
+  for (;;)
+    printf("for body %d\n", i);
+    // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println("for body {}", i);
+
+  for (printf("for init statement %d\n", i);;)
+    // CHECK-MESSAGES: [[@LINE-1]]:8: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println("for init statement {}", i);
+    ;;
+
+  for (int j = printf("for init statement %d\n", i);;)
+    ;;
+
+  for (; printf("for condition %d\n", i);)
+    ;;
+
+  for (;; printf("for expression %d\n", i))
+    // CHECK-MESSAGES: [[@LINE-1]]:11: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println("for expression {}", i)
+    ;;
+
+  for (auto C : "foo")
+    printf("ranged-for body %d\n", i);
+    // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println("ranged-for body {}", i);
+
+  switch (1) {
+  case 1:
+    printf("switch case body %d\n", i);
+    // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println("switch case body {}", i);
+    break;
+  default:
+    printf("switch default body %d\n", i);
+    // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println("switch default body {}", i);
+    break;
+  }
+
+  try {
+    printf("try body %d\n", i);
+    // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println("try body {}", i);
+  } catch (int) {
+    printf("catch body %d\n", i);
+    // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println("catch body {}", i);
+  }
+
+  (printf("Parenthesised expression %d\n", i));
+  // CHECK-MESSAGES: [[@LINE-1]]:4: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: (std::println("Parenthesised expression {}", i));
+
+  // Ideally we would convert these two, but the current check doesn't cope with
+  // that.
+  (void)printf("cast to void %d\n", i);
+  // CHECK-MESSAGES-NOT: [[@LINE-1]]:9: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOT: std::println("cast to void {}", i);
+
+  static_cast<void>(printf("static_cast to void %d\n", i));
+  // CHECK-MESSAGES-NOT: [[@LINE-1]]:9: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOT: std::println("static cast to void {}", i);
+
+  const int x = ({ printf("GCC statement expression using return value immediately %d\n", i); });
+  const int y = ({ const int y = printf("GCC statement expression using return value immediately %d\n", i); y; });
+
+  // Ideally we would convert this one, but the current check doesn't cope with
+  // that.
+  ({ printf("GCC statement expression with unused result %d\n", i); });
+  // CHECK-MESSAGES-NOT: [[@LINE-1]]:6: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES-NOT: std::println("GCC statement expression with unused result {}", i);
+
+  return printf("Return value used in return\n");
+}
+
+int fprintf_uses_return_value(int choice) {
+  const int i = fprintf(stderr, "Return value assigned to variable %d\n", 42);
+
+  if (choice == 0)
+    fprintf(stderr, "if body %d\n", i);
+    // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'fprintf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println(stderr, "if body {}", i);
+  else if (choice == 1)
+    fprintf(stderr, "else if body %d\n", i);
+    // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'fprintf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println(stderr, "else if body {}", i);
+  else
+    fprintf(stderr, "else body %d\n", i);
+    // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'fprintf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println(stderr, "else body {}", i);
+
+  if (fprintf(stderr, "Return value used as boolean in if statement"))
+    if (fprintf(stderr, "Return value used in expression if statement") == 44)
+      if (const int j = fprintf(stderr, "Return value used in assignment in if statement"))
+        if (const int k = fprintf(stderr, "Return value used with initializer in if statement"); k == 44)
+          ;
+
+  int d = 0;
+  while (fprintf(stderr, "%d", d) < 2)
+    ++d;
+
+  while (true)
+    fprintf(stderr, "while body %d\n", i);
+    // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'fprintf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println(stderr, "while body {}", i);
+
+  do
+    fprintf(stderr, "do body %d\n", i);
+    // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'fprintf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println(stderr, "do body {}", i);
+  while (true);
+
+  for (;;)
+    fprintf(stderr, "for body %d\n", i);
+    // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'fprintf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println(stderr, "for body {}", i);
+
+  for (fprintf(stderr, "for init statement %d\n", i);;)
+    // CHECK-MESSAGES: [[@LINE-1]]:8: warning: use 'std::println' instead of 'fprintf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println(stderr, "for init statement {}", i);
+    ;;
+
+  for (int j = fprintf(stderr, "for init statement %d\n", i);;)
+    ;;
+
+  for (; fprintf(stderr, "for condition %d\n", i);)
+    ;;
+
+  for (;; fprintf(stderr, "for expression %d\n", i))
+    // CHECK-MESSAGES: [[@LINE-1]]:11: warning: use 'std::println' instead of 'fprintf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println(stderr, "for expression {}", i)
+    ;;
+
+  for (auto C : "foo")
+    fprintf(stderr, "ranged-for body %d\n", i);
+    // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'fprintf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println(stderr, "ranged-for body {}", i);
+
+  switch (1) {
+  case 1:
+    fprintf(stderr, "switch case body %d\n", i);
+    // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'fprintf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println(stderr, "switch case body {}", i);
+    break;
+  default:
+    fprintf(stderr, "switch default body %d\n", i);
+    // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'fprintf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println(stderr, "switch default body {}", i);
+    break;
+  }
+
+  try {
+    fprintf(stderr, "try body %d\n", i);
+    // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'fprintf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println(stderr, "try body {}", i);
+  } catch (int) {
+    fprintf(stderr, "catch body %d\n", i);
+    // CHECK-MESSAGES: [[@LINE-1]]:5: warning: use 'std::println' instead of 'fprintf' [modernize-use-std-print]
+    // CHECK-FIXES: std::println(stderr, "catch body {}", i);
+  }
+
+
+  (printf("Parenthesised expression %d\n", i));
+  // CHECK-MESSAGES: [[@LINE-1]]:4: warning: use 'std::println' instead of 'printf' [modernize-use-std-print]
+  // CHECK-FIXES: (std::println("Parenthesised expression {}", i));
+
+  // Ideally we would convert these two, but the current check doesn't cope with
+  // that.
+  (void)fprintf(stderr, "cast to void %d\n", i);
+  // CHECK-MESSAGES-NOT: [[@LINE-1]]:9: warning: use 'std::println' instead of 'fprintf' [modernize-use-std-print]
+  // CHECK-FIXES-NOT: std::println(stderr, "cast to void {}", i);
+
+  static_cast<void>(fprintf(stderr, "static_cast to void %d\n", i));
+  // CHECK-MESSAGES-NOT: [[@LINE-1]]:9: warning: use 'std::println' instead of 'fprintf' [modernize-use-std-print]
+  // CHECK-FIXES-NOT: std::println(stderr, "static cast to void {}", i);
+
+  const int x = ({ fprintf(stderr, "GCC statement expression using return value immediately %d\n", i); });
+  const int y = ({ const int y = fprintf(stderr, "GCC statement expression using return value immediately %d\n", i); y; });
+
+  // Ideally we would convert this one, but the current check doesn't cope with
+  // that.
+  ({ fprintf(stderr, "GCC statement expression with unused result %d\n", i); });
+  // CHECK-MESSAGES-NOT: [[@LINE-1]]:6: warning: use 'std::println' instead of 'fprintf' [modernize-use-std-print]
+  // CHECK-FIXES-NOT: std::println("GCC statement expression with unused result {}", i);
+
+  return fprintf(stderr, "Return value used in return\n");
+}
+
 void fprintf_simple() {
   fprintf(stderr, "Hello");
   // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use 'std::print' instead of 'fprintf' [modernize-use-std-print]


        


More information about the cfe-commits mailing list