set(LLVM_LINK_COMPONENTS support)
add_clang_library(clangTidyOpenMPModule
+ ExceptionEscapeCheck.cpp
OpenMPTidyModule.cpp
UseDefaultNoneCheck.cpp
clangASTMatchers
clangBasic
clangTidy
+ clangTidyUtils
)
--- /dev/null
+//===--- 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
--- /dev/null
+//===--- 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
#include "../ClangTidy.h"
#include "../ClangTidyModule.h"
#include "../ClangTidyModuleRegistry.h"
+#include "ExceptionEscapeCheck.h"
#include "UseDefaultNoneCheck.h"
namespace clang {
class OpenMPModule : public ClangTidyModule {
public:
void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
+ CheckFactories.registerCheck<ExceptionEscapeCheck>(
+ "openmp-exception-escape");
CheckFactories.registerCheck<UseDefaultNoneCheck>(
"openmp-use-default-none");
}
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) {
return analyzeDispatch(Func);
}
+ExceptionAnalyzer::ExceptionInfo
+ExceptionAnalyzer::analyze(const Stmt *Stmt) {
+ return analyzeDispatch(Stmt);
+}
+
} // namespace utils
} // namespace tidy
}
ExceptionInfo analyze(const FunctionDecl *Func);
+ ExceptionInfo analyze(const Stmt *Stmt);
private:
ExceptionInfo
llvm::SmallSet<const FunctionDecl *, 32> &CallStack);
ExceptionInfo analyzeImpl(const FunctionDecl *Func);
+ ExceptionInfo analyzeImpl(const Stmt *Stmt);
template <typename T> ExceptionInfo analyzeDispatch(const T *Node);
<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.
objc-avoid-spinlock
objc-forbidden-subclassing
objc-property-declaration
+ openmp-exception-escape
openmp-use-default-none
performance-faster-string-find
performance-for-range-copy
--- /dev/null
+.. 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 undefined.
+
+FIXME: this check does not model SEH, ``setjmp``/``longjmp``.
+
+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.
--- /dev/null
+// 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
--- /dev/null
+// 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 (...) {
+ }
+ }
+}