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 normal. +/// 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,160 @@ +//===--- 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/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cert { + +namespace { +constexpr StringRef ExFunUnderscoreExit = "_Exit"; +constexpr StringRef ExFunExit = "exit"; +constexpr StringRef ExFunQuickExit = "quick_exit"; + +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 == ExFunUnderscoreExit || FnName == ExFunExit || + FnName == ExFunQuickExit; +} + +constexpr StringRef JmpFumLongjmp = "longjmp"; + +/// Only 'longjmp' is considered. +bool isJumpFunction(StringRef FnName) { return FnName == JmpFumLongjmp; } + +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. + auto begin() const -> decltype(CalledFunctions.begin()) { + return CalledFunctions.begin(); + } + auto end() const -> decltype(CalledFunctions.end()) { + return CalledFunctions.end(); + } +}; +} // namespace + +/// Match register-function calls, that has handler-functions as their first +/// argument. +void ExitHandlerCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + callExpr( + callee(functionDecl(hasAnyName(RegFunAtexit, RegFunAtQuickExit))), + hasArgument( + 0, // the first argument is the handler function + declRefExpr(hasDeclaration(functionDecl().bind("handler_decl"))) + .bind("handler_expr"))) + .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; + + // Collect all the referenced FunctionDecls. + Collector.TraverseStmt(CurrentDefWithBody->getBody()); + // Add called functions to the worklist. + llvm::copy(Collector, std::back_inserter(CalledFunctions)); + // Reset the ASTVisitor instance results. + Collector.clear(); + } +} + +} // 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,130 @@ +.. title:: clang-tidy - cert-env32-c + +cert-env32-c +============ + +This check implements SEI CERT rule ENV32-C. +Description source: ``_ + +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. (See SIG30-C. Call only asynchronous-safe functions within signal handlers.) + +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; +} + 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 + +// CHECK-NOTES: :[[@LINE+2]]:1: note: handler function declared here +// CHECK-NOTES: :[[@LINE+1]]:1: note: handler function declared here +void call__Exit() { + _Exit(0); + // CHECK-NOTES: :[[@LINE-1]]:3: note: exit function called here + // CHECK-NOTES: :[[@LINE-2]]:3: note: exit function called here + // CHECK-NOTES: :[[@LINE-3]]:3: note: exit function called here + // CHECK-NOTES: :[[@LINE-4]]:3: note: exit function called here + // CHECK-NOTES: :[[@LINE-5]]:3: note: exit function called here + // CHECK-NOTES: :[[@LINE-6]]:3: note: exit function called here + // CHECK-NOTES: :[[@LINE-7]]:3: note: exit function called here + // CHECK-NOTES: :[[@LINE-8]]:3: note: exit function called here +} + +// CHECK-NOTES: :[[@LINE+2]]:1: note: handler function declared here +// CHECK-NOTES: :[[@LINE+1]]:1: note: handler function declared here +void call_call__Exit() { + call__Exit(); +} + +extern int unknown__Exit_flag; + +// CHECK-NOTES: :[[@LINE+2]]:1: note: handler function declared here +// CHECK-NOTES: :[[@LINE+1]]:1: note: handler function declared here +void call__Exit_conditionally() { + if (unknown__Exit_flag) + call__Exit(); +} + +// CHECK-NOTES: :[[@LINE+2]]:1: note: handler function declared here +// CHECK-NOTES: :[[@LINE+1]]:1: note: handler function declared here +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] + (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] +}; + +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] + (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] +}; + +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] + (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] +}; + +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] + (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] +}; + +// Non-compliant solutions calling exit + +// CHECK-NOTES: :[[@LINE+2]]:1: note: handler function declared here +// CHECK-NOTES: :[[@LINE+1]]:1: note: handler function declared here +void call_exit() { + exit(0); + // CHECK-NOTES: :[[@LINE-1]]:3: note: exit function called here + // CHECK-NOTES: :[[@LINE-2]]:3: note: exit function called here + // CHECK-NOTES: :[[@LINE-3]]:3: note: exit function called here + // CHECK-NOTES: :[[@LINE-4]]:3: note: exit function called here + // CHECK-NOTES: :[[@LINE-5]]:3: note: exit function called here + // CHECK-NOTES: :[[@LINE-6]]:3: note: exit function called here + // CHECK-NOTES: :[[@LINE-7]]:3: note: exit function called here + // CHECK-NOTES: :[[@LINE-8]]:3: note: exit function called here +} + +// CHECK-NOTES: :[[@LINE+2]]:1: note: handler function declared here +// CHECK-NOTES: :[[@LINE+1]]:1: note: handler function declared here +void call_call_exit() { + call_exit(); +} + +extern int unknown_exit_flag; + +// CHECK-NOTES: :[[@LINE+2]]:1: note: handler function declared here +// CHECK-NOTES: :[[@LINE+1]]:1: note: handler function declared here +void call_exit_conditionally() { + if (unknown_exit_flag) + call_exit(); +} + +// CHECK-NOTES: :[[@LINE+2]]:1: note: handler function declared here +// CHECK-NOTES: :[[@LINE+1]]:1: note: handler function declared here +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] + (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] +}; + +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] + (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] +}; + +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] + (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] +}; + +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] + (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] +}; + +// Non-compliant solutions calling quick_exit + +// CHECK-NOTES: :[[@LINE+2]]:1: note: handler function declared here +// CHECK-NOTES: :[[@LINE+1]]:1: note: handler function declared here +void call_quick_exit() { + quick_exit(0); + // CHECK-NOTES: :[[@LINE-1]]:3: note: exit function called here + // CHECK-NOTES: :[[@LINE-2]]:3: note: exit function called here + // CHECK-NOTES: :[[@LINE-3]]:3: note: exit function called here + // CHECK-NOTES: :[[@LINE-4]]:3: note: exit function called here + // CHECK-NOTES: :[[@LINE-5]]:3: note: exit function called here + // CHECK-NOTES: :[[@LINE-6]]:3: note: exit function called here + // CHECK-NOTES: :[[@LINE-7]]:3: note: exit function called here + // CHECK-NOTES: :[[@LINE-8]]:3: note: exit function called here +} + +// CHECK-NOTES: :[[@LINE+2]]:1: note: handler function declared here +// CHECK-NOTES: :[[@LINE+1]]:1: note: handler function declared here +void call_call_quick_exit() { + call_quick_exit(); +} + +extern int unknown_quick_exit_flag; + +// CHECK-NOTES: :[[@LINE+2]]:1: note: handler function declared here +// CHECK-NOTES: :[[@LINE+1]]:1: note: handler function declared here +void call_quick_exit_conditionally() { + if (unknown_quick_exit_flag) + call_quick_exit(); +} + +// CHECK-NOTES: :[[@LINE+2]]:1: note: handler function declared here +// CHECK-NOTES: :[[@LINE+1]]:1: note: handler function declared here +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] + (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] +}; + +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] + (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] +}; + +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] + (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] +}; + +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] + (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] +}; + +// Mixed compliant and non-compliant solutions. + +// CHECK-NOTES: :[[@LINE+2]]:1: note: handler function declared here +// CHECK-NOTES: :[[@LINE+1]]:1: note: handler function declared here +void call_exit2() { + exit(0); + // CHECK-NOTES: :[[@LINE-1]]:3: note: exit function called here + // CHECK-NOTES: :[[@LINE-2]]:3: note: exit function called here +} + +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] + (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] +} + +// Non-compliant solution using recursion. + +extern int unknown_recursion_flag; + +// CHECK-NOTES: :[[@LINE+2]]:1: note: handler function declared here +// CHECK-NOTES: :[[@LINE+1]]:1: note: handler function declared here +void recursive_hander() { + if (unknown_recursion_flag > 0) { + --unknown_recursion_flag; + recursive_hander(); + } + exit(0); + // CHECK-NOTES: :[[@LINE-1]]:3: note: exit function called here + // CHECK-NOTES: :[[@LINE-2]]:3: note: exit function called here +} + +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] + (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] +} + +// Non-compliant solution using jumps. + +jmp_buf env; +extern int unknown_error_flag; + +// CHECK-NOTES: :[[@LINE+2]]:1: note: handler function declared here +// CHECK-NOTES: :[[@LINE+1]]:1: note: handler function declared here +void longjmp_handler() { + if (setjmp(env)) { + // error handling + } + + if (unknown_error_flag) { + longjmp(env, 255); + // CHECK-NOTES: :[[@LINE-1]]:5: note: jump function called here + // CHECK-NOTES: :[[@LINE-2]]:5: note: jump function called here + } +} + +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] + (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] +} 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",