lebedev.ri updated this revision to Diff 191892.
lebedev.ri marked 15 inline comments as done.
lebedev.ri added a comment.

Addressed some nits.


Repository:
  rCTE Clang Tools Extra

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D59466/new/

https://reviews.llvm.org/D59466

Files:
  clang-tidy/openmp/CMakeLists.txt
  clang-tidy/openmp/ExceptionEscapeCheck.cpp
  clang-tidy/openmp/ExceptionEscapeCheck.h
  clang-tidy/openmp/OpenMPTidyModule.cpp
  clang-tidy/utils/ExceptionAnalyzer.cpp
  clang-tidy/utils/ExceptionAnalyzer.h
  docs/ReleaseNotes.rst
  docs/clang-tidy/checks/list.rst
  docs/clang-tidy/checks/openmp-exception-escape.rst
  test/clang-tidy/bugprone-exception-escape-openmp.cpp
  test/clang-tidy/openmp-exception-escape.cpp

Index: test/clang-tidy/openmp-exception-escape.cpp
===================================================================
--- /dev/null
+++ test/clang-tidy/openmp-exception-escape.cpp
@@ -0,0 +1,132 @@
+// RUN: %check_clang_tidy %s openmp-exception-escape %t -- -extra-arg=-fopenmp=libomp -extra-arg=-fexceptions -config="{CheckOptions: [{key: openmp-exception-escape.IgnoredExceptions, value: 'ignored, ignored2'}]}" --
+
+int thrower() {
+  throw 42;
+}
+
+class ignored {};
+class ignored2 {};
+namespace std {
+class bad_alloc {};
+} // namespace std
+
+void parallel() {
+#pragma omp parallel
+  thrower();
+  // CHECK-MESSAGES: :[[@LINE-2]]:9: warning: an exception thrown inside of the OpenMP 'parallel' region is not caught in that same region.
+}
+
+void ignore() {
+#pragma omp parallel
+  throw ignored();
+}
+
+void ignore2() {
+#pragma omp parallel
+  throw ignored2();
+}
+
+void standalone_directive() {
+#pragma omp taskwait
+  throw ignored(); // not structured block
+}
+
+void ignore_alloc() {
+#pragma omp parallel
+  throw std::bad_alloc();
+}
+
+void parallel_caught() {
+#pragma omp parallel
+  {
+    try {
+      thrower();
+    } catch (...) {
+    }
+  }
+}
+
+void for_header(const int a) {
+  // Only the body of the loop counts.
+#pragma omp for
+  for (int i = 0; i < thrower(); i++)
+    ;
+}
+
+void forloop(const int a) {
+#pragma omp for
+  for (int i = 0; i < a; i++)
+    thrower();
+  // CHECK-MESSAGES: :[[@LINE-3]]:9: warning: an exception thrown inside of the OpenMP 'for' region is not caught in that same region.
+}
+
+void parallel_forloop(const int a) {
+#pragma omp parallel
+  {
+#pragma omp for
+    for (int i = 0; i < a; i++)
+      thrower();
+    thrower();
+    // CHECK-MESSAGES: :[[@LINE-6]]:9: warning: an exception thrown inside of the OpenMP 'parallel' region is not caught in that same region.
+    // CHECK-MESSAGES: :[[@LINE-5]]:9: warning: an exception thrown inside of the OpenMP 'for' region is not caught in that same region.
+  }
+}
+
+void parallel_forloop_caught(const int a) {
+#pragma omp parallel
+  {
+#pragma omp for
+    for (int i = 0; i < a; i++) {
+      try {
+        thrower();
+      } catch (...) {
+      }
+    }
+    thrower();
+    // CHECK-MESSAGES: :[[@LINE-10]]:9: warning: an exception thrown inside of the OpenMP 'parallel' region is not caught in that same region.
+  }
+}
+
+void parallel_caught_forloop(const int a) {
+#pragma omp parallel
+  {
+#pragma omp for
+    for (int i = 0; i < a; i++)
+      thrower();
+    try {
+      thrower();
+    } catch (...) {
+    }
+    // CHECK-MESSAGES: :[[@LINE-7]]:9: warning: an exception thrown inside of the OpenMP 'for' region is not caught in that same region.
+  }
+}
+
+void parallel_outercaught_forloop(const int a) {
+#pragma omp parallel
+  {
+    try {
+#pragma omp for
+      for (int i = 0; i < a; i++)
+        thrower();
+      thrower();
+    } catch (...) {
+    }
+    // CHECK-MESSAGES: :[[@LINE-6]]:9: warning: an exception thrown inside of the OpenMP 'for' region is not caught in that same region.
+  }
+}
+
+void parallel_outercaught_forloop_caught(const int a) {
+#pragma omp parallel
+  {
+    try {
+#pragma omp for
+      for (int i = 0; i < a; i++) {
+        try {
+          thrower();
+        } catch (...) {
+        }
+      }
+    } catch (...) {
+    }
+  }
+}
Index: test/clang-tidy/bugprone-exception-escape-openmp.cpp
===================================================================
--- /dev/null
+++ test/clang-tidy/bugprone-exception-escape-openmp.cpp
@@ -0,0 +1,29 @@
+// RUN: %check_clang_tidy %s bugprone-exception-escape %t -- -extra-arg=-fopenmp=libomp -extra-arg=-fexceptions --
+
+int thrower() {
+  throw 42;
+}
+
+void ok_parallel() {
+#pragma omp parallel
+  thrower();
+}
+
+void bad_for_header_XFAIL(const int a) noexcept {
+#pragma omp for
+  for (int i = 0; i < thrower(); i++)
+    ;
+  // FIXME: this really should be caught by bugprone-exception-escape.
+  // https://bugs.llvm.org/show_bug.cgi?id=41102
+}
+
+void ok_forloop(const int a) {
+#pragma omp for
+  for (int i = 0; i < a; i++)
+    thrower();
+}
+
+void some_exception_just_so_that_check_clang_tidy_shuts_up() noexcept {
+  thrower();
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:6: warning: an exception may be thrown in function 'some_exception_just_so_that_check_clang_tidy_shuts_up' which should not throw exceptions
Index: docs/clang-tidy/checks/openmp-exception-escape.rst
===================================================================
--- /dev/null
+++ docs/clang-tidy/checks/openmp-exception-escape.rst
@@ -0,0 +1,24 @@
+.. title:: clang-tidy - openmp-exception-escape
+
+openmp-exception-escape
+=======================
+
+Analyzes OpenMP Structured Blocks and checks that no exception escapes
+out of the Structured Block it was thrown in.
+
+As per the OpenMP specification, a structured block is an executable statement,
+possibly compound, with a single entry at the top and a single exit at the
+bottom. Which means, ``throw`` may not be used to to 'exit' out of the
+structured block. If an exception is not caught in the same structured block
+it was thrown in, the behaviour is unspecified,
+the program will likely terminate.
+
+WARNING! This check may be expensive on large source files.
+
+Options
+-------
+
+.. option:: IgnoredExceptions
+
+   Comma-separated list containing type names which are not counted as thrown
+   exceptions in the check. Default value is an empty string.
Index: docs/clang-tidy/checks/list.rst
===================================================================
--- docs/clang-tidy/checks/list.rst
+++ docs/clang-tidy/checks/list.rst
@@ -227,6 +227,7 @@
    objc-avoid-spinlock
    objc-forbidden-subclassing
    objc-property-declaration
+   openmp-exception-escape
    openmp-use-default-none
    performance-faster-string-find
    performance-for-range-copy
Index: docs/ReleaseNotes.rst
===================================================================
--- docs/ReleaseNotes.rst
+++ docs/ReleaseNotes.rst
@@ -130,6 +130,12 @@
   <clang-tidy/checks/modernize-use-override>` now supports `OverrideSpelling`
   and `FinalSpelling` options.
 
+- New :doc:`openmp-exception-escape
+  <clang-tidy/checks/openmp-exception-escape>` check.
+
+  Analyzes OpenMP Structured Blocks and checks that no exception escapes
+  out of the Structured Block it was thrown in.
+
 - New :doc:`openmp-use-default-none
   <clang-tidy/checks/openmp-use-default-none>` check.
 
Index: clang-tidy/utils/ExceptionAnalyzer.h
===================================================================
--- clang-tidy/utils/ExceptionAnalyzer.h
+++ clang-tidy/utils/ExceptionAnalyzer.h
@@ -129,6 +129,7 @@
   }
 
   ExceptionInfo analyze(const FunctionDecl *Func);
+  ExceptionInfo analyze(const Stmt *Stmt);
 
 private:
   ExceptionInfo
@@ -139,6 +140,7 @@
                   llvm::SmallSet<const FunctionDecl *, 32> &CallStack);
 
   ExceptionInfo analyzeImpl(const FunctionDecl *Func);
+  ExceptionInfo analyzeImpl(const Stmt *Stmt);
 
   template <typename T> ExceptionInfo analyzeDispatch(const T *Node);
 
Index: clang-tidy/utils/ExceptionAnalyzer.cpp
===================================================================
--- clang-tidy/utils/ExceptionAnalyzer.cpp
+++ clang-tidy/utils/ExceptionAnalyzer.cpp
@@ -224,6 +224,12 @@
   return ExceptionList;
 }
 
+ExceptionAnalyzer::ExceptionInfo
+ExceptionAnalyzer::analyzeImpl(const Stmt *Stmt) {
+  llvm::SmallSet<const FunctionDecl *, 32> CallStack;
+  return throwsException(Stmt, ExceptionInfo::Throwables(), CallStack);
+}
+
 template <typename T>
 ExceptionAnalyzer::ExceptionInfo
 ExceptionAnalyzer::analyzeDispatch(const T *Node) {
@@ -245,6 +251,11 @@
   return analyzeDispatch(Func);
 }
 
+ExceptionAnalyzer::ExceptionInfo
+ExceptionAnalyzer::analyze(const Stmt *Stmt) {
+  return analyzeDispatch(Stmt);
+}
+
 } // namespace utils
 } // namespace tidy
 
Index: clang-tidy/openmp/OpenMPTidyModule.cpp
===================================================================
--- clang-tidy/openmp/OpenMPTidyModule.cpp
+++ clang-tidy/openmp/OpenMPTidyModule.cpp
@@ -9,6 +9,7 @@
 #include "../ClangTidy.h"
 #include "../ClangTidyModule.h"
 #include "../ClangTidyModuleRegistry.h"
+#include "ExceptionEscapeCheck.h"
 #include "UseDefaultNoneCheck.h"
 
 namespace clang {
@@ -19,6 +20,8 @@
 class OpenMPModule : public ClangTidyModule {
 public:
   void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
+    CheckFactories.registerCheck<ExceptionEscapeCheck>(
+        "openmp-exception-escape");
     CheckFactories.registerCheck<UseDefaultNoneCheck>(
         "openmp-use-default-none");
   }
Index: clang-tidy/openmp/ExceptionEscapeCheck.h
===================================================================
--- /dev/null
+++ clang-tidy/openmp/ExceptionEscapeCheck.h
@@ -0,0 +1,41 @@
+//===--- ExceptionEscapeCheck.h - clang-tidy --------------------*- C++ -*-===//
+//
+// 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_OPENMP_EXCEPTIONESCAPECHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_OPENMP_EXCEPTIONESCAPECHECK_H
+
+#include "../ClangTidy.h"
+#include "../utils/ExceptionAnalyzer.h"
+
+namespace clang {
+namespace tidy {
+namespace openmp {
+
+/// Analyzes OpenMP Structured Blocks and checks that no exception escapes
+/// out of the Structured Block it was thrown in.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/openmp-exception-escape.html
+class ExceptionEscapeCheck : public ClangTidyCheck {
+public:
+  ExceptionEscapeCheck(StringRef Name, ClangTidyContext *Context);
+  void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+
+private:
+  std::string RawIgnoredExceptions;
+
+  utils::ExceptionAnalyzer Tracer;
+};
+
+} // namespace openmp
+} // namespace tidy
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_OPENMP_EXCEPTIONESCAPECHECK_H
Index: clang-tidy/openmp/ExceptionEscapeCheck.cpp
===================================================================
--- /dev/null
+++ clang-tidy/openmp/ExceptionEscapeCheck.cpp
@@ -0,0 +1,84 @@
+//===--- ExceptionEscapeCheck.cpp - clang-tidy ----------------------------===//
+//
+// 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 "ExceptionEscapeCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/OpenMPClause.h"
+#include "clang/AST/Stmt.h"
+#include "clang/AST/StmtOpenMP.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/ASTMatchers/ASTMatchersMacros.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang {
+namespace tidy {
+namespace openmp {
+
+ExceptionEscapeCheck::ExceptionEscapeCheck(StringRef Name,
+                                           ClangTidyContext *Context)
+    : ClangTidyCheck(Name, Context),
+      RawIgnoredExceptions(Options.get("IgnoredExceptions", "")) {
+  llvm::SmallVector<StringRef, 8> FunctionsThatShouldNotThrowVec,
+      IgnoredExceptionsVec;
+
+  llvm::StringSet<> IgnoredExceptions;
+  StringRef(RawIgnoredExceptions).split(IgnoredExceptionsVec, ",", -1, false);
+  llvm::transform(IgnoredExceptionsVec, IgnoredExceptionsVec.begin(),
+                  [](StringRef S) { return S.trim(); });
+  IgnoredExceptions.insert(IgnoredExceptionsVec.begin(),
+                           IgnoredExceptionsVec.end());
+  Tracer.ignoreExceptions(std::move(IgnoredExceptions));
+  Tracer.ignoreBadAlloc(true);
+}
+
+void ExceptionEscapeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+  Options.store(Opts, "IgnoredExceptions", RawIgnoredExceptions);
+}
+
+void ExceptionEscapeCheck::registerMatchers(MatchFinder *Finder) {
+  // Don't register the check if OpenMP is not enabled; the OpenMP pragmas are
+  // completely ignored then, so no OpenMP entires will be present in the AST.
+  if (!getLangOpts().OpenMP)
+    return;
+  // Similarly, if C++ Exceptions are not enabled, nothing to do.
+  if (!getLangOpts().CPlusPlus || !getLangOpts().CXXExceptions)
+    return;
+
+  Finder->addMatcher(ompExecutableDirective(
+                         unless(isStandaloneDirective()),
+                         hasStructuredBlock(stmt().bind("structured-block")))
+                         .bind("directive"),
+                     this);
+}
+
+void ExceptionEscapeCheck::check(const MatchFinder::MatchResult &Result) {
+  const auto *Directive =
+      Result.Nodes.getNodeAs<OMPExecutableDirective>("directive");
+  assert(Directive && "Expected to match some OpenMP Executable directive.");
+  const auto *StructuredBlock =
+      Result.Nodes.getNodeAs<Stmt>("structured-block");
+  assert(StructuredBlock && "Expected to get some OpenMP Structured Block.");
+
+  if (Tracer.analyze(StructuredBlock).getBehaviour() !=
+      utils::ExceptionAnalyzer::State::Throwing)
+    return; // No exceptions have been proven to escape out of the struc. block.
+
+  // FIXME: We should provide more information about the exact location where
+  // the exception is thrown, maybe the full path the exception escapes.
+
+  diag(Directive->getBeginLoc(),
+       "an exception thrown inside of the OpenMP '%0' region is not caught in "
+       "that same region.")
+      << getOpenMPDirectiveName(Directive->getDirectiveKind());
+}
+
+} // namespace openmp
+} // namespace tidy
+} // namespace clang
Index: clang-tidy/openmp/CMakeLists.txt
===================================================================
--- clang-tidy/openmp/CMakeLists.txt
+++ clang-tidy/openmp/CMakeLists.txt
@@ -1,6 +1,7 @@
 set(LLVM_LINK_COMPONENTS support)
 
 add_clang_library(clangTidyOpenMPModule
+  ExceptionEscapeCheck.cpp
   OpenMPTidyModule.cpp
   UseDefaultNoneCheck.cpp
 
@@ -9,4 +10,5 @@
   clangASTMatchers
   clangBasic
   clangTidy
+  clangTidyUtils
   )
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to