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 ) Index: clang-tidy/openmp/ExceptionEscapeCheck.h =================================================================== --- clang-tidy/openmp/ExceptionEscapeCheck.h +++ 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 =================================================================== --- clang-tidy/openmp/ExceptionEscapeCheck.cpp +++ 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 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("directive"); + assert(Directive && "Expected to match some OpenMP Executable directive."); + const auto *StructuredBlock = + Result.Nodes.getNodeAs("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/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( + "openmp-exception-escape"); CheckFactories.registerCheck( "openmp-use-default-none"); } 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 &CallStack); ExceptionInfo analyzeImpl(const FunctionDecl *Func); + ExceptionInfo analyzeImpl(const Stmt *Stmt); template 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 CallStack; + return throwsException(Stmt, ExceptionInfo::Throwables(), CallStack); +} + template 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: docs/ReleaseNotes.rst =================================================================== --- docs/ReleaseNotes.rst +++ docs/ReleaseNotes.rst @@ -130,6 +130,12 @@ ` now supports `OverrideSpelling` and `FinalSpelling` options. +- New :doc:`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 ` check. 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/clang-tidy/checks/openmp-exception-escape.rst =================================================================== --- docs/clang-tidy/checks/openmp-exception-escape.rst +++ docs/clang-tidy/checks/openmp-exception-escape.rst @@ -0,0 +1,25 @@ +.. 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. Index: test/clang-tidy/bugprone-exception-escape-openmp.cpp =================================================================== --- test/clang-tidy/bugprone-exception-escape-openmp.cpp +++ 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: test/clang-tidy/openmp-exception-escape.cpp =================================================================== --- test/clang-tidy/openmp-exception-escape.cpp +++ 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 (...) { + } + } +}