diff --git a/clang-tools-extra/- b/clang-tools-extra/- new file mode 100644 diff --git a/clang-tools-extra/clang-tidy/cuda/CMakeLists.txt b/clang-tools-extra/clang-tidy/cuda/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/cuda/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/cuda/CMakeLists.txt @@ -1,5 +1,6 @@ add_clang_library(clangTidyCudaModule CudaTidyModule.cpp + UnsafeApiCallCheck.cpp LINK_LIBS clangTidy clangTidyUtils diff --git a/clang-tools-extra/clang-tidy/cuda/CudaTidyModule.cpp b/clang-tools-extra/clang-tidy/cuda/CudaTidyModule.cpp --- a/clang-tools-extra/clang-tidy/cuda/CudaTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/cuda/CudaTidyModule.cpp @@ -7,9 +7,9 @@ //===----------------------------------------------------------------------===// #include "../ClangTidy.h" -#include "../ClangTidyCheck.h" #include "../ClangTidyModule.h" #include "../ClangTidyModuleRegistry.h" +#include "UnsafeApiCallCheck.h" using namespace clang::ast_matchers; @@ -19,7 +19,9 @@ class CudaModule : public ClangTidyModule { public: - void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {} + void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck("cuda-unsafe-api-call"); + } }; // Register the CudaTidyModule using this statically initialized variable. diff --git a/clang-tools-extra/clang-tidy/cuda/UnsafeApiCallCheck.h b/clang-tools-extra/clang-tidy/cuda/UnsafeApiCallCheck.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/cuda/UnsafeApiCallCheck.h @@ -0,0 +1,112 @@ +//===--- UnsafeApiCallCheck.h - 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CUDA_UNSAFEAPICALLCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CUDA_UNSAFEAPICALLCHECK_H + +#include "../ClangTidyCheck.h" +#include "llvm/ADT/StringSet.h" +#include +#include + +namespace clang { +namespace tidy { +namespace cuda { + +/// Checks for whether the possible errors with the CUDA API invocations have +/// been handled. +/// +/// Calls to CUDA API can sometimes fail to perform the action. This may happen +/// due to a driver malfunction, lack of permissions, lack of a GPU, or a +/// multitude of other reasons. Such errors are returned by those API calls and +/// should be handled in some way. +/// The check provides the following options: +/// - "HandlerName" (optional): +/// specifies the name of the function or the macro to which the return +/// value of the API call should be passed. This effectively automates the +/// process of adding the error checks in question for projects that have +/// such a mechanism implemented in them. +/// - "AcceptedHandlers" (optional): +/// a comma-separated list specifying the only accepted handling +/// functions/macros into which the error from the api call can be passed. +/// If not specified all ways to handle the error that do not just ignore +/// the output value are accepted. The handlers may have scope specifiers +/// included in them, but if so then the full qualified name (with all +/// namespaces explicitly stated) has to be provided (for the performance +/// sake). If the handler set in the "HandlerName" is not in the list of +/// accepted handlers then it gets added to it automatially. +/// +/// Since the behavior of the check is significantly different when the +/// "AcceptedHandlers" option is set, the implementation is essentially split +/// into 2 paths, as highlighted by the comments near declarations. +class UnsafeApiCallCheck : public ClangTidyCheck { + class PPCallbacks; + + // For gathering api calls with an unused value - only those nodes + // can have a FixItHint when we limit the accepted handlers. + // + // Only used when "AcceptedHandlers" is set + class UnusedValueCallback + : public clang::ast_matchers::MatchFinder::MatchCallback { + public: + UnusedValueCallback(UnsafeApiCallCheck *check) : Check(check) {} + void + run(const clang::ast_matchers::MatchFinder::MatchResult &Result) override; + void onStartOfTranslationUnit() override; + + private: + UnsafeApiCallCheck *Check; + }; + +public: + UnsafeApiCallCheck(llvm::StringRef Name, + clang::tidy::ClangTidyContext *Context); + + void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, + Preprocessor *ModuleExpanderPP) override; + void registerMatchers(clang::ast_matchers::MatchFinder *Finder) override; + void + check(const clang::ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + const std::string HandlerName; + + // Only used when "AcceptedHandlers" is not set + void registerUnusedValueMatchers(clang::ast_matchers::MatchFinder *Finder); + // Only used when "AcceptedHandlers" is set + void registerBadlyHandledMatchers(clang::ast_matchers::MatchFinder *Finder); + + // Only used when "AcceptedHandlers" is set + void + checkUnusedValue(const clang::ast_matchers::MatchFinder::MatchResult &Result); + // Only used when "AcceptedHandlers" is not set + void + checkBadHandler(const clang::ast_matchers::MatchFinder::MatchResult &Result); + + const std::string AcceptedHandlersList; // Data store for AcceptedHandlersSet + const llvm::StringSet AcceptedHandlersSet; + // Generates AcceptedHandlersSet from AcceptedHandlersList + static llvm::StringSet + splitAcceptedHandlers(const llvm::StringRef &AcceptedHandlers, + const llvm::StringRef &HandlerName); + bool limitAcceptedHandlers(); + + // Only used when "AcceptedHandlers" is set + std::unordered_set> + AcceptedHandlerMacroLocations; + std::unordered_set UnusedValueNodes; + std::unique_ptr UnusedValueCallbackInstance; +}; + +} // namespace cuda +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CUDA_UNSAFEAPICALLCHECK_H diff --git a/clang-tools-extra/clang-tidy/cuda/UnsafeApiCallCheck.cpp b/clang-tools-extra/clang-tidy/cuda/UnsafeApiCallCheck.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/cuda/UnsafeApiCallCheck.cpp @@ -0,0 +1,300 @@ +//===--- UnsafeApiCallCheck.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 "UnsafeApiCallCheck.h" +#include "../utils/Matchers.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Tooling/FixIt.h" + +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cuda { + +namespace { + +constexpr auto HandlerNameOptionName = "HandlerName"; +constexpr auto AcceptedHandlersOptionName = "AcceptedHandlers"; + +} // namespace + +UnsafeApiCallCheck::UnsafeApiCallCheck(llvm::StringRef Name, + clang::tidy::ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + HandlerName(Options.get(HandlerNameOptionName, "")), + AcceptedHandlersList(Options.get(AcceptedHandlersOptionName, "")), + AcceptedHandlersSet( + splitAcceptedHandlers(AcceptedHandlersList, HandlerName)), + AcceptedHandlerMacroLocations( + 8, [](const SourceLocation &sLoc) { return sLoc.getHashValue(); }) { + // If an empty string was inserted then that means that there was an empty + // accepted handler in the list + if (AcceptedHandlersSet.find("") != AcceptedHandlersSet.end()) { + configurationDiag( + "Empty handler name found in the list of accepted handlers", + DiagnosticIDs::Error); + } +} + +llvm::StringSet +UnsafeApiCallCheck::splitAcceptedHandlers( + const llvm::StringRef &AcceptedHandlers, + const llvm::StringRef &HandlerName) { + // Check the case for when the accepted handlers are empty since otherwise + // split(...) will still fill the vector with an empty element + if (AcceptedHandlers.trim().empty()) { + return llvm::StringSet(); + } + llvm::SmallVector AcceptedHandlersVector; + AcceptedHandlers.split(AcceptedHandlersVector, ','); + + llvm::StringSet AcceptedHandlersSet; + for (auto AcceptedHandler : AcceptedHandlersVector) { + AcceptedHandlersSet.insert(AcceptedHandler.trim()); + } + + // If the handler for FixItHints is set then add it to + if (!AcceptedHandlersSet.empty() && !HandlerName.empty()) { + AcceptedHandlersSet.insert(HandlerName); + } + + return AcceptedHandlersSet; +} + +void UnsafeApiCallCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, HandlerNameOptionName, HandlerName); + Options.store(Opts, AcceptedHandlersOptionName, AcceptedHandlersList); +} + +inline bool UnsafeApiCallCheck::limitAcceptedHandlers() { + return !AcceptedHandlersSet.empty(); +} + +// Used for finding the occurences of accepted handler macros in the source +// code. +class UnsafeApiCallCheck::PPCallbacks : public clang::PPCallbacks { +public: + PPCallbacks(UnsafeApiCallCheck *Check) : Check(Check) {} + + void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD, + SourceRange Range, const MacroArgs *Args) override { + if (Check->AcceptedHandlersSet.find( + MacroNameTok.getIdentifierInfo()->getName()) != + Check->AcceptedHandlersSet.end()) { + Check->AcceptedHandlerMacroLocations.insert(MacroNameTok.getLocation()); + } + } + +private: + UnsafeApiCallCheck *Check; +}; + +void UnsafeApiCallCheck::registerPPCallbacks(const SourceManager &SM, + Preprocessor *PP, + Preprocessor *ModuleExpanderPP) { + if (limitAcceptedHandlers()) { + ModuleExpanderPP->addPPCallbacks(std::make_unique(this)); + } +} + +namespace { + +// Check if the declaration is in a specific header based on a condition +AST_MATCHER_P(Decl, isInSourceFile, std::function, + SourceFileNameCond) { + auto Loc = Node.getLocation(); + const auto &SM = Finder->getASTContext().getSourceManager(); + while (Loc.isValid()) { + if (SourceFileNameCond(SM.getFilename(Loc))) { + return true; + } + Loc = SM.getIncludeLoc(SM.getFileID(Loc)); + } + return false; +} + +// Check if the name of the declaration matches a specific condition +AST_MATCHER_P(NamedDecl, hasName, std::function, + DeclNameCond) { + return DeclNameCond(Node.getName()); +} + +// Check if the fully qualified name of the declaration matches a specific +// condition +AST_MATCHER_P(NamedDecl, hasQualName, std::function, + DeclNameCond) { + return DeclNameCond(Node.getQualifiedNameAsString()); +} + +constexpr auto UnusedValueBinding = "UnusedValueCall"; +constexpr auto badlyHandledBinding = "badlyHandledCall"; + +// Common matchers for both unlimited and limited accepted handlers. +const auto HostFunction = functionDecl(unless(anyOf( + hasAttr(attr::CUDADevice), + hasAttr(attr::CUDAGlobal)))); // CUDA API cannot be called from device code +const auto ApiCallExpression = callExpr( + callee(functionDecl(isInSourceFile([](StringRef FileName) { + return FileName.endswith("cuda_runtime.h") || + FileName.endswith("cuda_runtime_wrapper.h"); + }), // All CUDA API is included from the cuda_runtime.h + // header or __cuda_runtime_wrapper.h + returns(asString("cudaError_t"))))); + +} // namespace + +void UnsafeApiCallCheck::UnusedValueCallback::run( + const MatchFinder::MatchResult &Result) { + auto Node = Result.Nodes.getNodeAs(UnusedValueBinding); + assert(Node); + Check->UnusedValueNodes.insert(Node); +} + +void UnsafeApiCallCheck::UnusedValueCallback::onStartOfTranslationUnit() { + Check->UnusedValueNodes.clear(); +} + +void UnsafeApiCallCheck::registerMatchers(MatchFinder *Finder) { + if (limitAcceptedHandlers()) { + registerBadlyHandledMatchers(Finder); + } else { + registerUnusedValueMatchers(Finder); + } +} + +void UnsafeApiCallCheck::registerUnusedValueMatchers(MatchFinder *Finder) { + const auto UnusedValue = + matchers::isValueUnused(stmt(ApiCallExpression.bind(UnusedValueBinding))); + Finder->addMatcher(functionDecl(HostFunction, hasBody(UnusedValue)), this); +} + +void UnsafeApiCallCheck::registerBadlyHandledMatchers(MatchFinder *Finder) { + const auto UnusedValue = + matchers::isValueUnused(stmt(ApiCallExpression.bind(UnusedValueBinding))); + UnusedValueCallbackInstance = std::make_unique(this); + Finder->addMatcher(functionDecl(HostFunction, hasBody(UnusedValue)), + UnusedValueCallbackInstance.get()); + + const auto AcceptedHandlerPred = [this](const StringRef &Name) { + return AcceptedHandlersSet.contains(Name); + }; + + const auto AcceptedHandlerDecl = functionDecl( + anyOf(hasName(AcceptedHandlerPred), hasQualName(AcceptedHandlerPred))); + const auto AcceptedHandlerParent = callExpr(callee(AcceptedHandlerDecl)); + + Finder->addMatcher( + functionDecl( + HostFunction, + forEachDescendant(stmt(ApiCallExpression.bind(badlyHandledBinding), + unless(hasParent(AcceptedHandlerParent))))), + this); +} + +namespace { + +constexpr auto HandlerMsg = "Consider wrapping it with a call to " + "an error handler:"; +constexpr auto NoHandlerMsg = + "Consider adding logic to check if an error has " + "been returned or specify the error handler for this project."; + +inline bool isStmtInMacro(const Stmt *const Stmt) { + return Stmt->getBeginLoc().isInvalid() || Stmt->getBeginLoc().isMacroID() || + Stmt->getEndLoc().isInvalid() || Stmt->getEndLoc().isMacroID(); +} + +} // namespace + +void UnsafeApiCallCheck::check(const MatchFinder::MatchResult &Result) { + if (limitAcceptedHandlers()) { + checkBadHandler(Result); + } else { + checkUnusedValue(Result); + } +} + +void UnsafeApiCallCheck::checkUnusedValue( + const MatchFinder::MatchResult &Result) { + const std::string MessagePrefix = "Unchecked CUDA API call. "; + + const auto ApiCallNode = Result.Nodes.getNodeAs(UnusedValueBinding); + assert(ApiCallNode); + + // This disables the check for arguments inside macros, since we assume that + // such a macro is intended as a handler (even if it just passes the argument + // right through) + if (Result.SourceManager->isMacroArgExpansion(ApiCallNode->getBeginLoc())) { + return; + } + + if (HandlerName.empty()) { + diag(ApiCallNode->getBeginLoc(), MessagePrefix + NoHandlerMsg); + } else if (isStmtInMacro(ApiCallNode)) { + diag(ApiCallNode->getBeginLoc(), + MessagePrefix + "Consider wrapping it with a call to `" + HandlerName + + "`"); + } else { + diag(ApiCallNode->getBeginLoc(), MessagePrefix + HandlerMsg) + << FixItHint::CreateReplacement( + ApiCallNode->getSourceRange(), + (HandlerName + "(" + + tooling::fixit::getText(ApiCallNode->getSourceRange(), + *Result.Context) + + ")") + .str()); + } +} + +void UnsafeApiCallCheck::checkBadHandler( + const MatchFinder::MatchResult &Result) { + const std::string MessagePrefix = "CUDA API call not checked properly. "; + + const auto ApiCallNode = Result.Nodes.getNodeAs(badlyHandledBinding); + assert(ApiCallNode); + + const auto ApiCallNodeMacroLocation = Result.SourceManager->getExpansionLoc( + Result.SourceManager->getMacroArgExpandedLocation( + ApiCallNode->getBeginLoc())); + + // This disables the check for arguments inside macros, since we assume that + // such a macro is intended as a handler (even if it just passes the argument + // right through) + if (Result.SourceManager->isMacroArgExpansion(ApiCallNode->getBeginLoc()) && + AcceptedHandlerMacroLocations.find(ApiCallNodeMacroLocation) != + AcceptedHandlerMacroLocations.end()) { + return; + } + + if (HandlerName.empty()) { + diag(ApiCallNode->getBeginLoc(), MessagePrefix + NoHandlerMsg); + } else if (isStmtInMacro(ApiCallNode) || + UnusedValueNodes.find(ApiCallNode) == UnusedValueNodes.end()) { + diag(ApiCallNode->getBeginLoc(), + MessagePrefix + "Consider wrapping it with a call to `" + HandlerName + + "`"); + } else { + diag(ApiCallNode->getBeginLoc(), MessagePrefix + HandlerMsg) + << FixItHint::CreateReplacement( + ApiCallNode->getSourceRange(), + (HandlerName + "(" + + tooling::fixit::getText(ApiCallNode->getSourceRange(), + *Result.Context) + + ")") + .str()); + } +} + +} // namespace cuda +} // 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 @@ -106,6 +106,11 @@ Warns when a struct or class uses const or reference (lvalue or rvalue) data members. +- New :doc:`cuda-unsafe-api-call ` check. + + Warns whenever the error from CUDA API call is ignored/not handled with a set handler + and provides fixes for it. + New check aliases ^^^^^^^^^^^^^^^^^ diff --git a/clang-tools-extra/docs/clang-tidy/checks/cuda/unsafe-api-call.rst b/clang-tools-extra/docs/clang-tidy/checks/cuda/unsafe-api-call.rst new file mode 100644 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/cuda/unsafe-api-call.rst @@ -0,0 +1,61 @@ +.. title:: clang-tidy - cuda-unsafe-api-call + +cuda-unsafe-api-call +==================== + +Finds usages of CUDA API where the error returned by the API function is not +handled in any way. It has narrower specification of what it allows and what it +doesn't than the +:doc:`bugprone-unused-return-value <../bugprone/unused-return-value>` +check, which makes it useful for more applications. + +Specification +------------- + +A function is considered to be a part of CUDA API if: + +- it is included in a file that ends with either ``cuda_runtime.h`` or + ``cuda_runtime_wrapper.h`` (those headers are automatically included from the + during CUDA code compilation) + +- its return type is ``cudaError_t`` + +If a call to a function like that is made, it has to be used in another +statement, for example passed as a function argument, assigned to a variable or +used in an if statement. The only exception is passing the value to a macro, +which is considered a valid way to handle the error even if the macro does not +use the return value of the call (this is to allow dummy marker macros that +just pass the value through). It is recommended that a project-wide handler(s) +is set to handle such errors, but this is not a default requirement of the check +(though it can be set with the check's options). + +Example: + +.. code-block:: c++ + + void foo() { + cudaDeviceReset(); + } + +results in the following warnings:: + + 1 warning generated when compiling for host. + test.cu:2:3: warning: Unchecked CUDA API call. Consider adding logic to check if an error has been returned or specify the error handler for this project. [cuda-unsafe-api-call] + cudaDeviceReset(); + ^ + +Options +------- + +.. option:: HandlerName + + The name of the function or macro that should be used in fix it hints to + handle the return value of an API call. + +.. option:: AcceptedHandlers + + The list of handler functions or macros that are allowed for the specific + project. If specified, the only valid way to handle the error returned + by a CUDA API call is to pass it as one of the arguments to said handlers. + If the HandlerName option is also specified then it will be implicitly + added as one of the accepted handlers. 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 @@ -199,6 +199,7 @@ `cppcoreguidelines-pro-type-vararg `_, `cppcoreguidelines-slicing `_, `cppcoreguidelines-special-member-functions `_, + `cuda-unsafe-api-call `_, "Yes" `cppcoreguidelines-virtual-class-destructor `_, "Yes" `darwin-avoid-spinlock `_, `darwin-dispatch-once-nonstatic `_, "Yes" diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cuda/cuda.h b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cuda/cuda.h --- a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cuda/cuda.h +++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cuda/cuda.h @@ -12,7 +12,10 @@ }; typedef struct cudaStream *cudaStream_t; -typedef enum cudaError {} cudaError_t; +typedef enum cudaError { + cudaErrorInvalidValue, + cudaErrorMemoryAllocation +} cudaError_t; extern "C" int cudaConfigureCall(dim3 gridSize, dim3 blockSize, size_t sharedSize = 0, cudaStream_t stream = 0); diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cuda/cuda_runtime.h b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cuda/cuda_runtime.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cuda/cuda_runtime.h @@ -0,0 +1,3 @@ +#include "cuda.h" + +cudaError_t cudaDeviceReset(); diff --git a/clang-tools-extra/test/clang-tidy/checkers/cuda/unsafe-api-call-function-handler.cu b/clang-tools-extra/test/clang-tidy/checkers/cuda/unsafe-api-call-function-handler.cu new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/cuda/unsafe-api-call-function-handler.cu @@ -0,0 +1,71 @@ +// RUN: %check_clang_tidy %s cuda-unsafe-api-call %t -- \ +// RUN: -config="{CheckOptions: \ +// RUN: [{key: cuda-unsafe-api-call.HandlerName, \ +// RUN: value: 'cudaHandler'}, \ +// RUN: {key: cuda-unsafe-api-call.AcceptedHandlers, \ +// RUN: value: 'CUDA_HANDLER, DUMMY_CUDA_HANDLER, \ +// RUN: alternative::cudaAlternativeHandler, \ +// RUN: cudaOtherAlternativeHandler, bad::cudaBadHandler'}] \ +// RUN: }" \ +// RUN: -- -isystem %clang_tidy_headers -std=c++14 +#include + +#define DUMMY_CUDA_HANDLER(stmt) stmt +#define CUDA_HANDLER(stmt) do {auto err = stmt;} while(0) +#define API_CALL() do {cudaDeviceReset();} while(0) +#define HANDLED_API_CALL() do {int err2 = cudaDeviceReset();} while(0) + +void cudaHandler(); +void cudaHandler(cudaError_t error); +void badCudaHandler(cudaError_t error); + +namespace alternative { + +void cudaAlternativeHandler(cudaError_t error); + +void cudaOtherAlternativeHandler(cudaError_t error); + +} // namespace alternative + +void bad() { + API_CALL(); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: CUDA API call not checked properly. + // There isn't supposed to be a fix here since it's a macro call + + HANDLED_API_CALL(); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: CUDA API call not checked properly. + // There isn't supposed to be a fix here since it's a macro call + + cudaDeviceReset(); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: CUDA API call not checked properly. + // CHECK-FIXES: {{^}} cudaHandler(cudaDeviceReset());{{$}} + cudaHandler(); + + if (true) + cudaDeviceReset(); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: CUDA API call not checked properly. + // CHECK-FIXES: {{^}} cudaHandler(cudaDeviceReset());{{$}} + + badCudaHandler(cudaDeviceReset()); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: CUDA API call not checked properly. + // There isn't supposed to be a fix here since the result value is not unused + + int err = cudaDeviceReset(); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: CUDA API call not checked properly. + // There isn't supposed to be a fix here since the result value is not unused + + if (cudaDeviceReset()) { + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: CUDA API call not checked properly. + // There isn't supposed to be a fix here since the result value is not unused + return; + } + +} + +void good() { + cudaHandler(cudaDeviceReset()); + alternative::cudaAlternativeHandler(cudaDeviceReset()); + alternative::cudaOtherAlternativeHandler(cudaDeviceReset()); + CUDA_HANDLER(cudaDeviceReset() + 1); + DUMMY_CUDA_HANDLER(cudaDeviceReset()); +} diff --git a/clang-tools-extra/test/clang-tidy/checkers/cuda/unsafe-api-call-macro-handler.cu b/clang-tools-extra/test/clang-tidy/checkers/cuda/unsafe-api-call-macro-handler.cu new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/cuda/unsafe-api-call-macro-handler.cu @@ -0,0 +1,96 @@ +// RUN: %check_clang_tidy %s cuda-unsafe-api-call %t -- \ +// RUN: -config="{CheckOptions: \ +// RUN: [{key: cuda-unsafe-api-call.HandlerName, \ +// RUN: value: 'CUDA_HANDLER'}] \ +// RUN: }" \ +// RUN: -- -isystem %clang_tidy_headers -std=c++14 +#include + +class DummyContainer { + public: + int* begin(); + int* end(); +}; + +#define DUMMY_CUDA_HANDLER(stmt) stmt +#define CUDA_HANDLER(stmt) do {auto err = stmt;} while(0) +#define API_CALL() do {cudaDeviceReset();} while(0) + +void errorCheck(); +void errorCheck(cudaError_t error); + +void bad() { + API_CALL(); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Unchecked CUDA API call. + // There isn't supposed to be a fix here since it's a macro call + + cudaDeviceReset(); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Unchecked CUDA API call. + // CHECK-FIXES: {{^}} CUDA_HANDLER(cudaDeviceReset());{{$}} + errorCheck(); + + if (true) + cudaDeviceReset(); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Unchecked CUDA API call. + // CHECK-FIXES: {{^}} CUDA_HANDLER(cudaDeviceReset());{{$}} + + while (true) + cudaDeviceReset(); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Unchecked CUDA API call. + // CHECK-FIXES: {{^}} CUDA_HANDLER(cudaDeviceReset());{{$}} + + do + cudaDeviceReset(); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Unchecked CUDA API call. + // CHECK-FIXES: {{^}} CUDA_HANDLER(cudaDeviceReset());{{$}} + while(false); + + switch (0) { + case 0: + cudaDeviceReset(); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Unchecked CUDA API call. + // CHECK-FIXES: {{^}} CUDA_HANDLER(cudaDeviceReset());{{$}} + } + + for( + cudaDeviceReset(); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Unchecked CUDA API call. + // CHECK-FIXES: {{^}} CUDA_HANDLER(cudaDeviceReset());{{$}} + ; + cudaDeviceReset() + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Unchecked CUDA API call. + // CHECK-FIXES: {{^}} CUDA_HANDLER(cudaDeviceReset()){{$}} + ) cudaDeviceReset(); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Unchecked CUDA API call. + // CHECK-FIXES: {{^}} ) CUDA_HANDLER(cudaDeviceReset());{{$}} + + for(int i : DummyContainer()) + cudaDeviceReset(); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Unchecked CUDA API call. + // CHECK-FIXES: {{^}} CUDA_HANDLER(cudaDeviceReset());{{$}} + + auto x = ({ + cudaDeviceReset(); + // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: Unchecked CUDA API call. + // CHECK-FIXES: {{^}} CUDA_HANDLER(cudaDeviceReset());{{$}} + true; + }); +} + +int good() { + DUMMY_CUDA_HANDLER(cudaDeviceReset()); + + if (cudaDeviceReset()) { + return 0; + } + + switch (cudaDeviceReset()) { + case cudaErrorInvalidValue: return 1; + case cudaErrorMemoryAllocation: return 2; + default: return 3; + } + + auto err = ({cudaDeviceReset();}); + // NOTE: We don't check that `errorCheck()` actually handles the error; we just assume it does. + errorCheck(cudaDeviceReset()); +}