diff --git a/clang-tools-extra/clang-tidy/cert/CERTTidyModule.cpp b/clang-tools-extra/clang-tidy/cert/CERTTidyModule.cpp --- a/clang-tools-extra/clang-tidy/cert/CERTTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/cert/CERTTidyModule.cpp @@ -24,6 +24,7 @@ #include "CommandProcessorCheck.h" #include "DefaultOperatorNewAlignmentCheck.h" #include "DontModifyStdNamespaceCheck.h" +#include "ExitHandlerCheck.h" #include "FloatLoopCounter.h" #include "LimitedRandomnessCheck.h" #include "MutatingCopyCheck.h" @@ -95,6 +96,7 @@ CheckFactories.registerCheck( "cert-dcl37-c"); // ENV + CheckFactories.registerCheck("cert-env32-c"); CheckFactories.registerCheck("cert-env33-c"); // FLP CheckFactories.registerCheck("cert-flp30-c"); diff --git a/clang-tools-extra/clang-tidy/cert/CMakeLists.txt b/clang-tools-extra/clang-tidy/cert/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/cert/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/cert/CMakeLists.txt @@ -8,6 +8,7 @@ CommandProcessorCheck.cpp DefaultOperatorNewAlignmentCheck.cpp DontModifyStdNamespaceCheck.cpp + ExitHandlerCheck.cpp FloatLoopCounter.cpp LimitedRandomnessCheck.cpp MutatingCopyCheck.cpp diff --git a/clang-tools-extra/clang-tidy/cert/ExitHandlerCheck.h b/clang-tools-extra/clang-tidy/cert/ExitHandlerCheck.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/cert/ExitHandlerCheck.h @@ -0,0 +1,41 @@ +//===--- ExitHandlerCheck.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_CERT_EXITHANDLERCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_EXITHANDLERCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang { +namespace tidy { +namespace cert { + +/// Checker for SEI CERT rule ENV32-C +/// All exit handlers must return normally. +/// Exit handlers must terminate by returning. It is important and potentially +/// safety-critical for all exit handlers to be allowed to perform their cleanup +/// actions. This is particularly true because the application programmer does +/// not always know about handlers that may have been installed by support +/// libraries. Two specific issues include nested calls to an exit function and +/// terminating a call to an exit handler by invoking longjmp. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cert-exit-handler-check.html +class ExitHandlerCheck : public ClangTidyCheck { +public: + ExitHandlerCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace cert +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CERT_EXITHANDLERCHECK_H diff --git a/clang-tools-extra/clang-tidy/cert/ExitHandlerCheck.cpp b/clang-tools-extra/clang-tidy/cert/ExitHandlerCheck.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/cert/ExitHandlerCheck.cpp @@ -0,0 +1,151 @@ +//===--- Env32CCheck.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 "ExitHandlerCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/SmallVector.h" +#include +#include +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cert { + +namespace { +constexpr StringRef RegFunAtexit = "atexit"; +constexpr StringRef RegFunAtQuickExit = "at_quick_exit"; + +/// The following functions are considered exit functions: +/// '_Exit' +/// 'exit' +/// 'quick_exit' +bool isExitFunction(StringRef FnName) { + return FnName == "_Exit" || FnName == "exit" || FnName == "quick_exit"; +} + +/// Only 'longjmp' is considered. +bool isJumpFunction(StringRef FnName) { return FnName == "longjmp"; } + +class CalledFunctionsCollector + : public RecursiveASTVisitor { + // The declarations and usages of encountered functions. + llvm::SmallVector, 32> + CalledFunctions; + +public: + bool VisitCallExpr(const CallExpr *CE) { + if (const auto *F = dyn_cast(CE->getCalleeDecl())) + CalledFunctions.emplace_back(F, CE); + return true; + } + + void clear() { CalledFunctions.clear(); } + + /// Iteration over the collector is iteration over the found FunctionDecls. + /// In order to allow moving from the underlying container, non-const + /// interators are allowed. + auto begin() { return CalledFunctions.begin(); } + auto end() { return CalledFunctions.end(); } +}; +} // namespace + +/// Match register-function calls, that has handler-functions as their first +/// argument. +void ExitHandlerCheck::registerMatchers(MatchFinder *Finder) { + const auto IsRegisterFunction = + callee(functionDecl(hasAnyName(RegFunAtexit, RegFunAtQuickExit))); + const auto HasHandlerAsFirstArg = hasArgument( + 0, declRefExpr(hasDeclaration(functionDecl().bind("handler_decl"))) + .bind("handler_expr")); + Finder->addMatcher( + callExpr(IsRegisterFunction, HasHandlerAsFirstArg).bind("register_call"), + this); +} + +/// Check if the callgraph of the handler-function contains any exit functions +/// or jump functions. +void ExitHandlerCheck::check(const MatchFinder::MatchResult &Result) { + const auto *RegisterCall = Result.Nodes.getNodeAs("register_call"); + const auto *HandlerDecl = + Result.Nodes.getNodeAs("handler_decl"); + const auto *HandlerExpr = Result.Nodes.getNodeAs("handler_expr"); + + // Visit each function encountered in the callgraph only once. + llvm::DenseSet SeenFunctions; + + // Reuse the ASTVistor instance that collects the called functions. + CalledFunctionsCollector Collector; + + // The worklist of the callgraph visitation algorithm. + std::deque> CalledFunctions{ + {HandlerDecl, HandlerExpr}}; + + // Visit the definition of every function referenced by the handler function, + // and look for exit-functions and jump calls. + while (!CalledFunctions.empty()) { + // Use the canonical declaration for uniquing. + const FunctionDecl *Current = + CalledFunctions.front().first->getCanonicalDecl(); + const Expr *CurrentUsage = CalledFunctions.front().second; + CalledFunctions.pop_front(); + + // Do not visit functions with same canonical declaration twice. + if (!SeenFunctions.insert(Current).second) + continue; + + // Check the name of the current function. + const StringRef CurrentName = Current->getName(); + if (isExitFunction(CurrentName)) { + // An exit-function is encountered somewhere in the callgraph of the + // handler. + diag(RegisterCall->getBeginLoc(), + "exit-handler potentially calls an exit function. Handlers should " + "terminate by returning"); + diag(HandlerDecl->getBeginLoc(), "handler function declared here", + DiagnosticIDs::Note); + diag(CurrentUsage->getBeginLoc(), "exit function called here", + DiagnosticIDs::Note); + break; + } + if (isJumpFunction(CurrentName)) { + // A jump function is encountered somewhere in the callgraph of the + // handler. + diag(RegisterCall->getSourceRange().getBegin(), + "exit-handler potentially calls a jump function. Handlers should " + "terminate by returning"); + diag(HandlerDecl->getBeginLoc(), "handler function declared here", + DiagnosticIDs::Note); + diag(CurrentUsage->getBeginLoc(), "jump function called here", + DiagnosticIDs::Note); + break; + } + + // Get the body of the encountered non-exit and non-longjmp function. + const FunctionDecl *CurrentDefWithBody; + if (!Current->hasBody(CurrentDefWithBody)) + continue; + + // Reset the ASTVisitor instance results. + Collector.clear(); + // Collect all the referenced FunctionDecls. + Collector.TraverseStmt(CurrentDefWithBody->getBody()); + // Move the called functions to the worklist. + std::move(Collector.begin(), Collector.end(), + std::back_inserter(CalledFunctions)); + } +} + +} // namespace cert +} // namespace tidy +} // namespace clang diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -82,6 +82,11 @@ Finds ``s.find(...) == string::npos`` comparisons (for various string-like types) and suggests replacing with ``absl::StrContains()``. +- New :doc:`cert-env32-c + ` check. + Finds functions registered by ``atexit`` and ``at_quick_exit`` that are calling + exit functions ``_Exit``, ``exit``, ``quick_exit`` or ``longjmp``. + - New :doc:`cppcoreguidelines-avoid-non-const-global-variables ` check. Finds non-const global variables as described in check I.2 of C++ Core diff --git a/clang-tools-extra/docs/clang-tidy/checks/cert-env32-c.rst b/clang-tools-extra/docs/clang-tidy/checks/cert-env32-c.rst new file mode 100644 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/cert-env32-c.rst @@ -0,0 +1,159 @@ +.. title:: clang-tidy - cert-env32-c + +cert-env32-c +============ + +This check implements SEI CERT rule ENV32-C by finding functions registered by +``atexit`` and ``at_quick_exit`` that are calling exit functions ``_Exit``, +``exit``, ``quick_exit`` and ``longjmp``. + +All exit handlers must return normally +-------------------------------------- + +The C Standard provides three functions that cause an application to terminate +normally: ``_Exit``, ``exit``, and ``quick_exit``. These are collectively called +exit functions. When the ``exit`` function is called, or control transfers out +of the ``main`` entry point function, functions registered with ``atexit`` are +called (but not ``at_quick_exit``). When the ``quick_exit`` function is called, +functions registered with ``at_quick_exit`` (but not ``atexit``) are called. +These functions are collectively called exit handlers. When the ``_Exit`` +function is called, no exit handlers or signal handlers are called. + +Exit handlers must terminate by returning. It is important and potentially +safety-critical for all exit handlers to be allowed to perform their cleanup +actions. This is particularly true because the application programmer does not +always know about handlers that may have been installed by support libraries. +Two specific issues include nested calls to an exit function and terminating a +call to an exit handler by invoking ``longjmp``. + +A nested call to an exit function is undefined behavior. (See undefined behavior +182.) This behavior can occur only when an exit function is invoked from an exit +handler or when an exit function is called from within a signal handler. + +If a call to the ``longjmp`` function is made that would terminate the call to a +function registered with ``atexit``, the behavior is undefined. + +Noncompliant Code Example + +In this noncompliant code example, the ``exit1`` and ``exit2`` functions are +registered by ``atexit`` to perform required cleanup upon program termination. +However, if ``some_condition`` evaluates to true, ``exit`` is called a second +time, resulting in undefined behavior. + +.. code-block:: c + + #include + + void exit1(void) { + /* ... Cleanup code ... */ + return; + } + + void exit2(void) { + extern int some_condition; + if (some_condition) { + /* ... More cleanup code ... */ + exit(0); + } + return; + } + + int main(void) { + if (atexit(exit1) != 0) { + /* Handle error */ + } + if (atexit(exit2) != 0) { + /* Handle error */ + } + /* ... Program code ... */ + return 0; + } + +Functions registered by the ``atexit`` function are called in the reverse order +from which they were registered. Consequently, if ``exit2`` exits in any way +other than by returning, ``exit1`` will not be executed. The same may also be +true for ``atexit`` handlers installed by support libraries. + +Compliant Solution + +A function that is registered as an exit handler by ``atexit`` must exit by +returning, as in this compliant solution: + +.. code-block:: c + + #include + + void exit1(void) { + /* ... Cleanup code ... */ + return; + } + + void exit2(void) { + extern int some_condition; + if (some_condition) { + /* ... More cleanup code ... */ + } + return; + } + + int main(void) { + if (atexit(exit1) != 0) { + /* Handle error */ + } + if (atexit(exit2) != 0) { + /* Handle error */ + } + /* ... Program code ... */ + return 0; + } + +Noncompliant Code Example + +In this noncompliant code example, ``exit1`` is registered by ``atexit`` so that +upon program termination, ``exit1`` is called. The ``exit1`` function jumps back +to ``main`` to return, with undefined results. + +.. code-block:: c + +#include +#include + +jmp_buf env; +int val; + +void exit1(void) { + longjmp(env, 1); +} + +int main(void) { + if (atexit(exit1) != 0) { + /* Handle error */ + } + if (setjmp(env) == 0) { + exit(0); + } else { + return 0; + } +} + +Compliant Solution + +This compliant solution does not call ``longjmp`` but instead returns from the +exit handler normally: + +.. code-block:: c + +#include + +void exit1(void) { + return; +} + +int main(void) { + if (atexit(exit1) != 0) { + /* Handle error */ + } + return 0; +} + +Description source: ``_ diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -102,6 +102,7 @@ `cert-dcl21-cpp `_, `cert-dcl50-cpp `_, `cert-dcl58-cpp `_, + `cert-env32-c `_, `cert-env33-c `_, `cert-err34-c `_, `cert-err52-cpp `_, diff --git a/clang-tools-extra/test/clang-tidy/checkers/cert-env32-c.c b/clang-tools-extra/test/clang-tidy/checkers/cert-env32-c.c new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/cert-env32-c.c @@ -0,0 +1,324 @@ +// RUN: %check_clang_tidy %s cert-env32-c %t + +// -------------- +// EXIT FUNCTIONS +// -------------- + +// No handlers are invoked when _Exit is called. +void _Exit(int __status); + +// Handlers registered by atexit are invoked in reverse order when exit is +// called. +void exit(int __status); + +// Handlers registered by at_quick_exit are invoked in reverse order when +// quick_exit is called. +void quick_exit(int __status); + +// -------------------- +// HANDLER REGISTRATION +// -------------------- + +// Register handlers to run when exit is called. +int atexit(void (*__func)(void)); + +// Register handlers to run when exit is called. +int at_quick_exit(void (*__func)(void)); + +// -------------- +// Setjmp/longjmp +// -------------- +// C99 requires jmp_buf to be an array type. +typedef int jmp_buf[1]; +int setjmp(jmp_buf); +void longjmp(jmp_buf, int); + +// Compliant solutions + +void cleanup1() { + // do cleanup +} + +void cleanup2() { + // do cleanup +} + +void test_atexit_single_compliant() { + (void)atexit(cleanup1); +} + +void test_atexit_multiple_compliant() { + (void)atexit(cleanup1); + (void)atexit(cleanup2); +} + +void test_at_quick_exit_single_compliant() { + (void)at_quick_exit(cleanup1); +} + +void test_at_quick_exit_multiple_compliant() { + (void)at_quick_exit(cleanup1); + (void)at_quick_exit(cleanup2); +} + +// Non-compliant solutions calling _Exit + +void call__Exit() { + _Exit(0); +} + +void call_call__Exit() { + call__Exit(); +} + +extern int unknown__Exit_flag; + +void call__Exit_conditionally() { + if (unknown__Exit_flag) + call__Exit(); +} + +void call_call__Exit_conditionally() { + call__Exit_conditionally(); +} + +void test__Exit_called_directly() { + (void)atexit(call__Exit); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls an exit function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-22]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-22]]:3: note: exit function called here + (void)at_quick_exit(call__Exit); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls an exit function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-26]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-26]]:3: note: exit function called here +}; + +void test__Exit_called_indirectly() { + (void)atexit(call_call__Exit); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls an exit function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-29]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-33]]:3: note: exit function called here + (void)at_quick_exit(call_call__Exit); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls an exit function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-33]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-37]]:3: note: exit function called here +}; + +void test_conditional__Exit_called_directly() { + (void)atexit(call__Exit_conditionally); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls an exit function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-34]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-44]]:3: note: exit function called here + (void)at_quick_exit(call__Exit_conditionally); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls an exit function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-38]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-48]]:3: note: exit function called here +}; + +void test_conditional__Exit_called_indirectly() { + (void)atexit(call_call__Exit_conditionally); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls an exit function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-40]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-55]]:3: note: exit function called here + (void)at_quick_exit(call_call__Exit_conditionally); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls an exit function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-44]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-59]]:3: note: exit function called here +}; + +// Non-compliant solutions calling exit + +void call_exit() { + exit(0); +} + +void call_call_exit() { + call_exit(); +} + +extern int unknown_exit_flag; + +void call_exit_conditionally() { + if (unknown_exit_flag) + call_exit(); +} + +void call_call_exit_conditionally() { + call_exit_conditionally(); +} + +void test_exit_called_directly() { + (void)atexit(call_exit); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls an exit function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-22]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-22]]:3: note: exit function called here + (void)at_quick_exit(call_exit); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls an exit function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-26]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-26]]:3: note: exit function called here +}; + +void test_exit_called_indirectly() { + (void)atexit(call_call_exit); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls an exit function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-29]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-33]]:3: note: exit function called here + (void)at_quick_exit(call_call_exit); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls an exit function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-33]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-37]]:3: note: exit function called here +}; + +void test_conditional_exit_called_directly() { + (void)atexit(call_exit_conditionally); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls an exit function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-34]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-44]]:3: note: exit function called here + (void)at_quick_exit(call_exit_conditionally); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls an exit function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-38]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-48]]:3: note: exit function called here +}; + +void test_conditional_exit_called_indirectly() { + (void)atexit(call_call_exit_conditionally); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls an exit function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-40]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-55]]:3: note: exit function called here + (void)at_quick_exit(call_call_exit_conditionally); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls an exit function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-44]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-59]]:3: note: exit function called here +}; + +// Non-compliant solutions calling quick_exit + +void call_quick_exit() { + quick_exit(0); +} + +void call_call_quick_exit() { + call_quick_exit(); +} + +extern int unknown_quick_exit_flag; + +void call_quick_exit_conditionally() { + if (unknown_quick_exit_flag) + call_quick_exit(); +} + +void call_call_quick_exit_conditionally() { + call_quick_exit_conditionally(); +} + +void test_quick_exit_called_directly() { + (void)atexit(call_quick_exit); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls an exit function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-22]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-22]]:3: note: exit function called here + (void)at_quick_exit(call_quick_exit); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls an exit function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-26]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-26]]:3: note: exit function called here +}; + +void test_quick_exit_called_indirectly() { + (void)atexit(call_call_quick_exit); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls an exit function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-29]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-33]]:3: note: exit function called here + (void)at_quick_exit(call_call_quick_exit); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls an exit function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-33]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-37]]:3: note: exit function called here +}; + +void test_conditional_quick_exit_called_directly() { + (void)atexit(call_quick_exit_conditionally); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls an exit function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-34]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-44]]:3: note: exit function called here + (void)at_quick_exit(call_quick_exit_conditionally); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls an exit function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-38]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-48]]:3: note: exit function called here +}; + +void test_conditional_quick_exit_called_indirectly() { + (void)atexit(call_call_quick_exit_conditionally); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls an exit function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-40]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-55]]:3: note: exit function called here + (void)at_quick_exit(call_call_quick_exit_conditionally); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls an exit function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-44]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-59]]:3: note: exit function called here +}; + +// Mixed compliant and non-compliant solutions. + +void call_exit2() { + exit(0); +} + +void test_compliant_and_noncompliant_atexits() { + (void)atexit(cleanup1); + (void)atexit(call_exit2); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls an exit function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-8]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-8]]:3: note: exit function called here + (void)at_quick_exit(call_exit2); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls an exit function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-12]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-12]]:3: note: exit function called here +} + +// Non-compliant solution using recursion. + +extern int unknown_recursion_flag; + +void recursive_hander() { + if (unknown_recursion_flag > 0) { + --unknown_recursion_flag; + recursive_hander(); + } + exit(0); +} + +void test_recursive_handler() { + (void)atexit(recursive_hander); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls an exit function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-11]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-7]]:3: note: exit function called here + (void)at_quick_exit(recursive_hander); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls an exit function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-15]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-11]]:3: note: exit function called here +} + +// Non-compliant solution using jumps. + +jmp_buf env; +extern int unknown_error_flag; + +void longjmp_handler() { + if (setjmp(env)) { + // error handling + } + + if (unknown_error_flag) { + longjmp(env, 255); + } +} + +void test_longjmp_handler() { + (void)atexit(longjmp_handler); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls a jump function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-13]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-8]]:5: note: jump function called here + (void)at_quick_exit(longjmp_handler); + // CHECK-NOTES: :[[@LINE-1]]:9: warning: exit-handler potentially calls a jump function. Handlers should terminate by returning [cert-env32-c] + // CHECK-NOTES: :[[@LINE-17]]:1: note: handler function declared here + // CHECK-NOTES: :[[@LINE-12]]:5: note: jump function called here +} diff --git a/llvm/utils/gn/secondary/clang-tools-extra/clang-tidy/cert/BUILD.gn b/llvm/utils/gn/secondary/clang-tools-extra/clang-tidy/cert/BUILD.gn --- a/llvm/utils/gn/secondary/clang-tools-extra/clang-tidy/cert/BUILD.gn +++ b/llvm/utils/gn/secondary/clang-tools-extra/clang-tidy/cert/BUILD.gn @@ -19,6 +19,7 @@ "CommandProcessorCheck.cpp", "DefaultOperatorNewAlignmentCheck.cpp", "DontModifyStdNamespaceCheck.cpp", + "ExitHandlerCheck.cpp", "FloatLoopCounter.cpp", "LimitedRandomnessCheck.cpp", "MutatingCopyCheck.cpp",