nickdesaulniers created this revision.
Herald added a subscriber: hiraditya.
Herald added a project: All.
nickdesaulniers requested review of this revision.
Herald added projects: clang, LLVM.
Herald added subscribers: llvm-commits, cfe-commits.

Debugging functions that have fallen through to the next function is not
fun. So much so that the Linux kernel has a host utility called objtool
that can report such cases of fallthrough (for architectures objtool
supports). Example:

  drivers/media/i2c/m5mols/m5mols.o: warning: objtool:
  m5mols_set_fmt() falls through to next function __cfi_m5mols_open()

With this new diagnostic, -Wfunction-fallthrough, we can produce a
similar diagnostic:

  drivers/media/i2c/m5mols/m5mols_core.c:573:12: warning: function
  'm5mols_set_fmt' falls through to next function
  [-Wfunction-fallthrough]
  static int m5mols_set_fmt(struct v4l2_subdev *sd,
             ^

This can help diagnose the improper usage of __builtin_unreachable(). It
can also help us spot potential codegen bugs in LLVM itself.

Example issues faced in the past:
https://github.com/ClangBuiltLinux/linux/issues?q=is%3Aissue+falls+label%3A%22%5BTOOL%5D+objtool%22+
Feature request:
https://lore.kernel.org/llvm/CAHk-=wgtsdkybmb1jym5vmhmcd9j9uzr0mn7boym_ludrp+...@mail.gmail.com/


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D146466

Files:
  clang/docs/ReleaseNotes.rst
  clang/include/clang/Basic/DiagnosticFrontendKinds.td
  clang/include/clang/Basic/DiagnosticGroups.td
  clang/lib/CodeGen/CodeGenAction.cpp
  clang/test/Frontend/backend-function-fallthrough.c
  llvm/include/llvm/IR/DiagnosticInfo.h
  llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
  llvm/lib/IR/DiagnosticInfo.cpp
  llvm/test/CodeGen/AArch64/function-fallthrough.ll

Index: llvm/test/CodeGen/AArch64/function-fallthrough.ll
===================================================================
--- /dev/null
+++ llvm/test/CodeGen/AArch64/function-fallthrough.ll
@@ -0,0 +1,8 @@
+; RUN: llc %s 2>&1 | FileCheck %s
+declare void @foo ()
+
+define void @bar () {
+; CHECK: warning: function 'bar' falls through to next function
+entry:
+  unreachable
+}
Index: llvm/lib/IR/DiagnosticInfo.cpp
===================================================================
--- llvm/lib/IR/DiagnosticInfo.cpp
+++ llvm/lib/IR/DiagnosticInfo.cpp
@@ -450,3 +450,7 @@
   if (!getNote().empty())
     DP << ": " << getNote();
 }
+
+void DiagnosticInfoFunctionFallthrough::print(DiagnosticPrinter &DP) const {
+  DP << "function '" << getFunction().getName() << "' falls through to next function";
+}
Index: llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
===================================================================
--- llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
+++ llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
@@ -66,6 +66,7 @@
 #include "llvm/IR/DataLayout.h"
 #include "llvm/IR/DebugInfoMetadata.h"
 #include "llvm/IR/DerivedTypes.h"
+#include "llvm/IR/DiagnosticInfo.h"
 #include "llvm/IR/EHPersonalities.h"
 #include "llvm/IR/Function.h"
 #include "llvm/IR/GCStrategy.h"
@@ -2388,6 +2389,30 @@
   return Res.first->second;
 }
 
+static void diagnoseFunctionFallthrough(MachineFunction &MF) {
+  bool FT = false;
+  MachineBasicBlock &LastMBB = MF.back();
+  if (LastMBB.canFallThrough())
+    FT = true;
+  // TODO: MachineBasicBlock::canFallThrough is too conservative; it will not
+  // catch this!
+  else if (LastMBB.empty())
+    FT = true;
+  // TODO: TII::isTailCall?
+  else if (LastMBB.succ_empty() && !LastMBB.isReturnBlock())
+    // TODO: MachineBasicBlock::canFallThrough is too conservative; it will not
+    // catch this!
+    // Don't call MachineBasicBlock::back() without a
+    // !MachineBasicBlock::empty() guard.
+    if (const Function *Callee = LastMBB.back().getCalledFunction())
+      FT = !Callee->hasFnAttribute(Attribute::NoReturn);
+  if (FT) {
+    Function &F = MF.getFunction();
+    DiagnosticInfoFunctionFallthrough D(F);
+    F.getContext().diagnose(D);
+  }
+}
+
 void AsmPrinter::SetupMachineFunction(MachineFunction &MF) {
   this->MF = &MF;
   const Function &F = MF.getFunction();
@@ -2436,6 +2461,8 @@
   }
 
   ORE = &getAnalysis<MachineOptimizationRemarkEmitterPass>().getORE();
+
+  diagnoseFunctionFallthrough(MF);
 }
 
 namespace {
Index: llvm/include/llvm/IR/DiagnosticInfo.h
===================================================================
--- llvm/include/llvm/IR/DiagnosticInfo.h
+++ llvm/include/llvm/IR/DiagnosticInfo.h
@@ -20,6 +20,7 @@
 #include "llvm/ADT/StringRef.h"
 #include "llvm/ADT/Twine.h"
 #include "llvm/IR/DebugLoc.h"
+#include "llvm/IR/Function.h"
 #include "llvm/Support/CBindingWrapping.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/SourceMgr.h"
@@ -38,7 +39,6 @@
 class DIFile;
 class DISubprogram;
 class CallInst;
-class Function;
 class Instruction;
 class InstructionCost;
 class Module;
@@ -86,6 +86,7 @@
   DK_SrcMgr,
   DK_DontCall,
   DK_MisExpect,
+  DK_FunctionFallthrough,
   DK_FirstPluginKind // Must be last value to work with
                      // getNextAvailablePluginDiagnosticKind
 };
@@ -1051,6 +1052,17 @@
   const Twine &Msg;
 };
 
+struct DiagnosticInfoFunctionFallthrough : public DiagnosticInfoWithLocationBase {
+  DiagnosticInfoFunctionFallthrough(Function &F) :
+    DiagnosticInfoWithLocationBase(DK_FunctionFallthrough, DS_Warning, F,
+                                   F.getSubprogram())
+  {}
+  static bool classof(const DiagnosticInfo *DI) {
+    return DI->getKind() == DK_FunctionFallthrough;
+  }
+  void print(DiagnosticPrinter &DP) const override;
+};
+
 static DiagnosticSeverity getDiagnosticSeverity(SourceMgr::DiagKind DK) {
   switch (DK) {
   case llvm::SourceMgr::DK_Error:
Index: clang/test/Frontend/backend-function-fallthrough.c
===================================================================
--- /dev/null
+++ clang/test/Frontend/backend-function-fallthrough.c
@@ -0,0 +1,13 @@
+// RUN: %clang_cc1 -O0 -verify -emit-codegen-only %s
+// RUN: %clang_cc1 -O2 -verify=expected,optimization -emit-codegen-only %s
+void foo (void);
+int bar (int x) { // optimization-warning {{function 'bar' falls through to next function}}
+  if (x) {
+    foo();
+    __builtin_unreachable();
+  }
+  return 42;
+}
+void baz (void) { // expected-warning {{function 'baz' falls through to next function}}
+  __builtin_unreachable();
+}
Index: clang/lib/CodeGen/CodeGenAction.cpp
===================================================================
--- clang/lib/CodeGen/CodeGenAction.cpp
+++ clang/lib/CodeGen/CodeGenAction.cpp
@@ -462,6 +462,7 @@
     /// Specialized handler for misexpect warnings.
     /// Note that misexpect remarks are emitted through ORE
     void MisExpectDiagHandler(const llvm::DiagnosticInfoMisExpect &D);
+    void FunctionFallthroughHandler(const DiagnosticInfoFunctionFallthrough &DI);
   };
 
   void BackendConsumer::anchor() {}
@@ -875,6 +876,11 @@
         << Filename << Line << Column;
 }
 
+void BackendConsumer::FunctionFallthroughHandler(const DiagnosticInfoFunctionFallthrough &DI) {
+  if (std::optional<FullSourceLoc> Loc = getFunctionSourceLocation(DI.getFunction()))
+    Diags.Report(*Loc, diag::warn_fe_function_fallthrough) << DI.getFunction().getName();
+}
+
 /// This function is invoked when the backend needs
 /// to report something to the user.
 void BackendConsumer::DiagnosticHandlerImpl(const DiagnosticInfo &DI) {
@@ -957,6 +963,9 @@
   case llvm::DK_MisExpect:
     MisExpectDiagHandler(cast<DiagnosticInfoMisExpect>(DI));
     return;
+  case llvm::DK_FunctionFallthrough:
+    FunctionFallthroughHandler(cast<DiagnosticInfoFunctionFallthrough>(DI));
+    return;
   default:
     // Plugin IDs are not bound to any value as they are set dynamically.
     ComputeDiagRemarkID(Severity, backend_plugin, DiagID);
Index: clang/include/clang/Basic/DiagnosticGroups.td
===================================================================
--- clang/include/clang/Basic/DiagnosticGroups.td
+++ clang/include/clang/Basic/DiagnosticGroups.td
@@ -1296,6 +1296,12 @@
 def BackendOptimizationRemarkAnalysis : DiagGroup<"pass-analysis">;
 def BackendOptimizationFailure : DiagGroup<"pass-failed">;
 def BackendWarningAttributes : DiagGroup<"attribute-warning">;
+def BackendWarningFunctionFallthrough : DiagGroup<"function-fallthrough">{
+  code Documentation = [{
+The function falls through to the next function. This can occur due to either
+the incorrect use of __builtin_unreachable() or potentially a bug in LLVM.
+}];
+}
 
 // Instrumentation based profiling warnings.
 def ProfileInstrMissing : DiagGroup<"profile-instr-missing">;
Index: clang/include/clang/Basic/DiagnosticFrontendKinds.td
===================================================================
--- clang/include/clang/Basic/DiagnosticFrontendKinds.td
+++ clang/include/clang/Basic/DiagnosticFrontendKinds.td
@@ -90,6 +90,9 @@
 def warn_fe_backend_warning_attr :
   Warning<"call to '%0' declared with 'warning' attribute: %1">, BackendInfo,
   InGroup<BackendWarningAttributes>;
+def warn_fe_function_fallthrough:
+    Warning<"function '%0' falls through to next function">,
+    InGroup<BackendWarningFunctionFallthrough>;
 
 def err_fe_invalid_code_complete_file : Error<
     "cannot locate code-completion file %0">, DefaultFatal;
Index: clang/docs/ReleaseNotes.rst
===================================================================
--- clang/docs/ReleaseNotes.rst
+++ clang/docs/ReleaseNotes.rst
@@ -179,6 +179,8 @@
 - Clang now avoids duplicate warnings on unreachable ``[[fallthrough]];`` statements
   previously issued from ``-Wunreachable-code`` and ``-Wunreachable-code-fallthrough``
   by prioritizing ``-Wunreachable-code-fallthrough``.
+- New diagnostic ``-Wfunction-fallthrough`` warns when a function falls through
+  to the next function.
 
 Bug Fixes in This Version
 -------------------------
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to