Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[clang][Sema] Combine fallout warnings to just one warning #127546

Merged
merged 12 commits into from
Feb 19, 2025

Conversation

foxtran
Copy link
Member

@foxtran foxtran commented Feb 17, 2025

This PR fixes FIXME: Combine fallout warnings to just one warning. at DiagnosticssSemaKinds.td.

Changes:

  • warn_maybe_falloff_nonvoid_function and warn_falloff_nonvoid_function, warn_maybe_falloff_nonvoid_coroutine and warn_falloff_nonvoid_coroutine, warn_maybe_falloff_nonvoid_lambda and warn_falloff_nonvoid_lambda were combined into warn_falloff_nonvoid,
  • err_maybe_falloff_nonvoid_block and err_falloff_nonvoid_block were combined into err_falloff_nonvoid
  • err_noreturn_block_has_return_expr and err_noreturn_lambda_has_return_expr were merged into err_noreturn_has_return_expr with the same semantics as warn_falloff_nonvoid or err_falloff_nonvoid.
  • Some code was removed as unnecessary now.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Feb 17, 2025
@llvmbot
Copy link
Member

llvmbot commented Feb 17, 2025

@llvm/pr-subscribers-clang

Author: None (foxtran)

Changes

This PR fixes FIXME: Combine fallout warnings to just one warning. at DiagnosticssSemaKinds.td.

Changes:

  • warn_maybe_falloff_nonvoid_function and warn_falloff_nonvoid_function, warn_maybe_falloff_nonvoid_block and warn_falloff_nonvoid_block, warn_maybe_falloff_nonvoid_coroutine and warn_falloff_nonvoid_coroutine, warn_maybe_falloff_nonvoid_lambda and warn_falloff_nonvoid_lambda were combined into warn_falloff_nonvoid,
  • err_maybe_falloff_nonvoid_block and err_falloff_nonvoid_block were combined into err_falloff_nonvoid.

It looks like current tests does not check these cases properly.


Full diff: https://github.com/llvm/llvm-project/pull/127546.diff

2 Files Affected:

  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+6-22)
  • (modified) clang/lib/Sema/AnalysisBasedWarnings.cpp (+40-68)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index f10af8f5bd6b2..4a7b33ebd01f4 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -713,13 +713,13 @@ def err_thread_non_global : Error<
 def err_thread_unsupported : Error<
   "thread-local storage is not supported for the current target">;
 
-// FIXME: Combine fallout warnings to just one warning.
-def warn_maybe_falloff_nonvoid_function : Warning<
-  "non-void function does not return a value in all control paths">,
-  InGroup<ReturnType>;
-def warn_falloff_nonvoid_function : Warning<
-  "non-void function does not return a value">,
+def warn_falloff_nonvoid : Warning<
+  "non-void %select{function|block|lambda|coroutine}0 "
+  "does not return a value%select{| in all control paths}1">,
   InGroup<ReturnType>;
+def err_falloff_nonvoid : Error<
+  "non-void %select{function|block|lambda|coroutine}0 "
+  "does not return a value%select{| in all control paths}1">;
 def warn_const_attr_with_pure_attr : Warning<
   "'const' attribute imposes more restrictions; 'pure' attribute ignored">,
   InGroup<IgnoredAttributes>;
@@ -727,16 +727,6 @@ def warn_pure_function_returns_void : Warning<
   "'%select{pure|const}0' attribute on function returning 'void'; attribute ignored">,
   InGroup<IgnoredAttributes>;
 
-def err_maybe_falloff_nonvoid_block : Error<
-  "non-void block does not return a value in all control paths">;
-def err_falloff_nonvoid_block : Error<
-  "non-void block does not return a value">;
-def warn_maybe_falloff_nonvoid_coroutine : Warning<
-  "non-void coroutine does not return a value in all control paths">,
-  InGroup<ReturnType>;
-def warn_falloff_nonvoid_coroutine : Warning<
-  "non-void coroutine does not return a value">,
-  InGroup<ReturnType>;
 def warn_suggest_noreturn_function : Warning<
   "%select{function|method}0 %1 could be declared with attribute 'noreturn'">,
   InGroup<MissingNoreturn>, DefaultIgnore;
@@ -8408,12 +8398,6 @@ let CategoryName = "Lambda Issue" in {
     "incomplete result type %0 in lambda expression">;
   def err_noreturn_lambda_has_return_expr : Error<
     "lambda declared 'noreturn' should not return">;
-  def warn_maybe_falloff_nonvoid_lambda : Warning<
-    "non-void lambda does not return a value in all control paths">,
-    InGroup<ReturnType>;
-  def warn_falloff_nonvoid_lambda : Warning<
-    "non-void lambda does not return a value">,
-    InGroup<ReturnType>;
   def err_access_lambda_capture : Error<
     // The ERRORs represent other special members that aren't constructors, in
     // hopes that someone will bother noticing and reporting if they appear
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 589869d018657..ef527374b811f 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -544,25 +544,17 @@ static ControlFlowKind CheckFallThrough(AnalysisDeclContext &AC) {
 namespace {
 
 struct CheckFallThroughDiagnostics {
-  unsigned diag_MaybeFallThrough_HasNoReturn;
-  unsigned diag_MaybeFallThrough_ReturnsNonVoid;
-  unsigned diag_AlwaysFallThrough_HasNoReturn;
-  unsigned diag_AlwaysFallThrough_ReturnsNonVoid;
+  unsigned diag_FallThrough_HasNoReturn;
+  unsigned diag_FallThrough_ReturnsNonVoid;
   unsigned diag_NeverFallThroughOrReturn;
-  enum { Function, Block, Lambda, Coroutine } funMode;
+  enum { Function = 0, Block, Lambda, Coroutine } funMode;
   SourceLocation FuncLoc;
 
   static CheckFallThroughDiagnostics MakeForFunction(const Decl *Func) {
     CheckFallThroughDiagnostics D;
     D.FuncLoc = Func->getLocation();
-    D.diag_MaybeFallThrough_HasNoReturn =
-      diag::warn_falloff_noreturn_function;
-    D.diag_MaybeFallThrough_ReturnsNonVoid =
-      diag::warn_maybe_falloff_nonvoid_function;
-    D.diag_AlwaysFallThrough_HasNoReturn =
-      diag::warn_falloff_noreturn_function;
-    D.diag_AlwaysFallThrough_ReturnsNonVoid =
-      diag::warn_falloff_nonvoid_function;
+    D.diag_FallThrough_HasNoReturn = diag::warn_falloff_noreturn_function;
+    D.diag_FallThrough_ReturnsNonVoid = diag::warn_falloff_nonvoid;
 
     // Don't suggest that virtual functions be marked "noreturn", since they
     // might be overridden by non-noreturn functions.
@@ -576,8 +568,7 @@ struct CheckFallThroughDiagnostics {
       isTemplateInstantiation = Function->isTemplateInstantiation();
 
     if (!isVirtualMethod && !isTemplateInstantiation)
-      D.diag_NeverFallThroughOrReturn =
-        diag::warn_suggest_noreturn_function;
+      D.diag_NeverFallThroughOrReturn = diag::warn_suggest_noreturn_function;
     else
       D.diag_NeverFallThroughOrReturn = 0;
 
@@ -588,12 +579,8 @@ struct CheckFallThroughDiagnostics {
   static CheckFallThroughDiagnostics MakeForCoroutine(const Decl *Func) {
     CheckFallThroughDiagnostics D;
     D.FuncLoc = Func->getLocation();
-    D.diag_MaybeFallThrough_HasNoReturn = 0;
-    D.diag_MaybeFallThrough_ReturnsNonVoid =
-        diag::warn_maybe_falloff_nonvoid_coroutine;
-    D.diag_AlwaysFallThrough_HasNoReturn = 0;
-    D.diag_AlwaysFallThrough_ReturnsNonVoid =
-        diag::warn_falloff_nonvoid_coroutine;
+    D.diag_FallThrough_HasNoReturn = 0;
+    D.diag_FallThrough_ReturnsNonVoid = diag::warn_falloff_nonvoid;
     D.diag_NeverFallThroughOrReturn = 0;
     D.funMode = Coroutine;
     return D;
@@ -601,14 +588,8 @@ struct CheckFallThroughDiagnostics {
 
   static CheckFallThroughDiagnostics MakeForBlock() {
     CheckFallThroughDiagnostics D;
-    D.diag_MaybeFallThrough_HasNoReturn =
-      diag::err_noreturn_block_has_return_expr;
-    D.diag_MaybeFallThrough_ReturnsNonVoid =
-      diag::err_maybe_falloff_nonvoid_block;
-    D.diag_AlwaysFallThrough_HasNoReturn =
-      diag::err_noreturn_block_has_return_expr;
-    D.diag_AlwaysFallThrough_ReturnsNonVoid =
-      diag::err_falloff_nonvoid_block;
+    D.diag_FallThrough_HasNoReturn = diag::err_noreturn_block_has_return_expr;
+    D.diag_FallThrough_ReturnsNonVoid = diag::err_falloff_nonvoid;
     D.diag_NeverFallThroughOrReturn = 0;
     D.funMode = Block;
     return D;
@@ -616,14 +597,8 @@ struct CheckFallThroughDiagnostics {
 
   static CheckFallThroughDiagnostics MakeForLambda() {
     CheckFallThroughDiagnostics D;
-    D.diag_MaybeFallThrough_HasNoReturn =
-      diag::err_noreturn_lambda_has_return_expr;
-    D.diag_MaybeFallThrough_ReturnsNonVoid =
-      diag::warn_maybe_falloff_nonvoid_lambda;
-    D.diag_AlwaysFallThrough_HasNoReturn =
-      diag::err_noreturn_lambda_has_return_expr;
-    D.diag_AlwaysFallThrough_ReturnsNonVoid =
-      diag::warn_falloff_nonvoid_lambda;
+    D.diag_FallThrough_HasNoReturn = diag::err_noreturn_lambda_has_return_expr;
+    D.diag_FallThrough_ReturnsNonVoid = diag::warn_falloff_nonvoid;
     D.diag_NeverFallThroughOrReturn = 0;
     D.funMode = Lambda;
     return D;
@@ -633,8 +608,7 @@ struct CheckFallThroughDiagnostics {
                         bool HasNoReturn) const {
     if (funMode == Function) {
       return (ReturnsVoid ||
-              D.isIgnored(diag::warn_maybe_falloff_nonvoid_function,
-                          FuncLoc)) &&
+              D.isIgnored(diag::warn_falloff_nonvoid, FuncLoc)) &&
              (!HasNoReturn ||
               D.isIgnored(diag::warn_noreturn_function_has_return_expr,
                           FuncLoc)) &&
@@ -643,9 +617,7 @@ struct CheckFallThroughDiagnostics {
     }
     if (funMode == Coroutine) {
       return (ReturnsVoid ||
-              D.isIgnored(diag::warn_maybe_falloff_nonvoid_function, FuncLoc) ||
-              D.isIgnored(diag::warn_maybe_falloff_nonvoid_coroutine,
-                          FuncLoc)) &&
+              D.isIgnored(diag::warn_falloff_nonvoid, FuncLoc)) &&
              (!HasNoReturn);
     }
     // For blocks / lambdas.
@@ -709,34 +681,34 @@ static void CheckFallThroughForBody(Sema &S, const Decl *D, const Stmt *Body,
 
   // Either in a function body compound statement, or a function-try-block.
   switch (CheckFallThrough(AC)) {
-    case UnknownFallThrough:
-      break;
+  case UnknownFallThrough:
+    break;
 
-    case MaybeFallThrough:
-      if (HasNoReturn)
-        EmitDiag(RBrace, CD.diag_MaybeFallThrough_HasNoReturn);
-      else if (!ReturnsVoid)
-        EmitDiag(RBrace, CD.diag_MaybeFallThrough_ReturnsNonVoid);
-      break;
-    case AlwaysFallThrough:
-      if (HasNoReturn)
-        EmitDiag(RBrace, CD.diag_AlwaysFallThrough_HasNoReturn);
-      else if (!ReturnsVoid)
-        EmitDiag(RBrace, CD.diag_AlwaysFallThrough_ReturnsNonVoid);
-      break;
-    case NeverFallThroughOrReturn:
-      if (ReturnsVoid && !HasNoReturn && CD.diag_NeverFallThroughOrReturn) {
-        if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
-          S.Diag(LBrace, CD.diag_NeverFallThroughOrReturn) << 0 << FD;
-        } else if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(D)) {
-          S.Diag(LBrace, CD.diag_NeverFallThroughOrReturn) << 1 << MD;
-        } else {
-          S.Diag(LBrace, CD.diag_NeverFallThroughOrReturn);
-        }
+  case MaybeFallThrough:
+    if (HasNoReturn)
+      EmitDiag(RBrace, CD.diag_FallThrough_HasNoReturn);
+    else if (!ReturnsVoid)
+      S.Diag(RBrace, CD.diag_FallThrough_ReturnsNonVoid) << CD.funMode << 1;
+    break;
+  case AlwaysFallThrough:
+    if (HasNoReturn)
+      EmitDiag(RBrace, CD.diag_FallThrough_HasNoReturn);
+    else if (!ReturnsVoid)
+      S.Diag(RBrace, CD.diag_FallThrough_ReturnsNonVoid) << CD.funMode << 0;
+    break;
+  case NeverFallThroughOrReturn:
+    if (ReturnsVoid && !HasNoReturn && CD.diag_NeverFallThroughOrReturn) {
+      if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
+        S.Diag(LBrace, CD.diag_NeverFallThroughOrReturn) << 0 << FD;
+      } else if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(D)) {
+        S.Diag(LBrace, CD.diag_NeverFallThroughOrReturn) << 1 << MD;
+      } else {
+        S.Diag(LBrace, CD.diag_NeverFallThroughOrReturn);
       }
-      break;
-    case NeverFallThrough:
-      break;
+    }
+    break;
+  case NeverFallThrough:
+    break;
   }
 }
 

Copy link
Member

@Sirraide Sirraide left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, thanks for the cleanup. I’m doing something related to this at the moment and this definitely helps with that ;Þ

I think this might be a good candidate for the new %enum_select thingy that @erichkeane added recently (see https://clang.llvm.org/docs/InternalsManual.html#formatting-a-diagnostic-argument for documentation if you’re not familiar w/ it).

@foxtran
Copy link
Member Author

foxtran commented Feb 18, 2025

I think this might be a good candidate for the new %enum_select thingy that @erichkeane added recently (see https://clang.llvm.org/docs/InternalsManual.html#formatting-a-diagnostic-argument for documentation if you’re not familiar w/ it).

Yep, thank you!

Will it have redefinition problem since I need to share this enum between two diagnostics?

@foxtran
Copy link
Member Author

foxtran commented Feb 18, 2025

Will it have redefinition problem since I need to share this enum between two diagnostics?

Unfortunately, yes...

def DupeNames1 : Error<"%enum_select<DupeName>{}0">;
def DupeNames2 : Error<"%enum_select<DupeName>{}0">;
// CHECK: error: Duplicate enumeration name 'DupeName'
// CHECK-NEXT: def DupeNames2
// CHECK: note: Previous diagnostic is here
// CHECK-NEXT: def DupeNames1

@Sirraide
Copy link
Member

Will it have redefinition problem since I need to share this enum between two diagnostics?

Hmm, that’s a good point. I think in that case, for now just use %enum_select for one of the two and make the other one a regular %select (you can still use the enum for both then you emit the diagnostic); just make sure the %select cases are in the same order in both diagnostics.

CC @AaronBallman @erichkeane Would it make sense to have proper support for reusing enums across diagnostics w/ %enum_select? I can think of a few other diagnostics that have more or less an equivalent set of cases. Maybe we can just emit the enum once and check that every diagnostic that uses %enum_select w/ the same enum has the same number of cases / the same enumerators?

@Sirraide
Copy link
Member

Maybe we can just emit the enum once and check that every diagnostic that uses %enum_select w/ the same enum has the same number of cases

That also reminds me: A while ago I ran into a bug where someone had added a few cases to one diagnotic but forgot to update another diagnostic that also needed those cases, and we ended up asserting because of that; if we require every diagnostic that uses %enum_select with the same enum to have the exact same number of cases then that might help avoid such bugs in the future.

@foxtran
Copy link
Member Author

foxtran commented Feb 18, 2025

Looks like I cannot have some variable with proper type generated from %enum_select.

So, for

"%enum_select<FunModes>{%Function{function}|%Block{block}|%Lambda{lambda}|%Coroutine{coroutine}}0"

I can not find corresponding type to save enum values into specific container (expected diag::FunModes).

@Sirraide
Copy link
Member

I can not find corresponding type to save enum values into specific container (expected diag::FunModes).

Ah, that’s because it emits an anonymous enum inside a namespace; just use unsigned I’d say.

@foxtran
Copy link
Member Author

foxtran commented Feb 18, 2025

https://godbolt.org/z/4EjMGoqTn

So, That is what I'd like to expect :D

@Sirraide
Copy link
Member

Sirraide commented Feb 18, 2025

https://godbolt.org/z/4EjMGoqTn

So, That is what I'd like to expect :D

What we’re currently emitting is more or less (grep for DIAG_ENUM):

namespace diag {
namespace FunModes {
enum {
    Function,
    // ...
};
}
}

@foxtran
Copy link
Member Author

foxtran commented Feb 18, 2025

Yep, I checked #122505

Copy link

github-actions bot commented Feb 18, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@foxtran
Copy link
Member Author

foxtran commented Feb 18, 2025

@Sirraide, CI should be happy now. I have merged invalid-noreturn warnings as well as now %enum_select is used.

@Sirraide
Copy link
Member

Also, just a heads-up, #127623 is likely going to be merged soon and introduce some merge conflicts for you in CheckFallThroughForBody(). The code that that pr touches is just deleted here, so that’s fine I think, but maybe make sure that none of the diag ids are 0 if the function being analysed is a coroutine.

(We generate an implicit return statement at the end of every coroutine, and if the coroutine is noreturn, then we try to emit a ‘noreturn function should not return’ warning. The correct behaviour is to just not emit a warning here at all in that case)

@Sirraide
Copy link
Member

#127623 has been merged.

@foxtran
Copy link
Member Author

foxtran commented Feb 18, 2025

@Sirraide, I still do not see how to call EmitDiag with coroutines :)

@Sirraide
Copy link
Member

Alright, that should really be everything at this point. Thanks!

@foxtran
Copy link
Member Author

foxtran commented Feb 19, 2025

@Sirraide, thank you!

@Sirraide Sirraide merged commit 9ebb618 into llvm:main Feb 19, 2025
8 checks passed
@llvm-ci
Copy link
Collaborator

llvm-ci commented Feb 19, 2025

LLVM Buildbot has detected a new failure on builder openmp-offload-libc-amdgpu-runtime running on omp-vega20-1 while building clang at step 7 "Add check check-offload".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/73/builds/13289

Here is the relevant piece of the build log for the reference
Step 7 (Add check check-offload) failure: 1200 seconds without output running [b'ninja', b'-j 32', b'check-offload'], attempting to kill
...
PASS: libomptarget :: x86_64-unknown-linux-gnu-LTO :: offloading/test_libc.cpp (1001 of 1011)
PASS: libomptarget :: x86_64-unknown-linux-gnu-LTO :: offloading/bug49779.cpp (1002 of 1011)
PASS: libomptarget :: x86_64-unknown-linux-gnu-LTO :: offloading/bug47654.cpp (1003 of 1011)
PASS: libomptarget :: x86_64-unknown-linux-gnu-LTO :: offloading/bug50022.cpp (1004 of 1011)
PASS: libomptarget :: x86_64-unknown-linux-gnu-LTO :: offloading/wtime.c (1005 of 1011)
PASS: libomptarget :: x86_64-unknown-linux-gnu :: offloading/bug49021.cpp (1006 of 1011)
PASS: libomptarget :: x86_64-unknown-linux-gnu :: offloading/std_complex_arithmetic.cpp (1007 of 1011)
PASS: libomptarget :: x86_64-unknown-linux-gnu-LTO :: offloading/complex_reduction.cpp (1008 of 1011)
PASS: libomptarget :: x86_64-unknown-linux-gnu-LTO :: offloading/bug49021.cpp (1009 of 1011)
PASS: libomptarget :: x86_64-unknown-linux-gnu-LTO :: offloading/std_complex_arithmetic.cpp (1010 of 1011)
command timed out: 1200 seconds without output running [b'ninja', b'-j 32', b'check-offload'], attempting to kill
process killed by signal 9
program finished with exit code -1
elapsedTime=1234.779719

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants