barcisz updated this revision to Diff 460184.
barcisz added a comment.

Documentation and small check-message-related bugfixes


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D133804/new/

https://reviews.llvm.org/D133804

Files:
  clang-tools-extra/-
  clang-tools-extra/clang-tidy/cuda/CMakeLists.txt
  clang-tools-extra/clang-tidy/cuda/CudaTidyModule.cpp
  clang-tools-extra/clang-tidy/cuda/UnsafeApiCallCheck.cpp
  clang-tools-extra/clang-tidy/cuda/UnsafeApiCallCheck.h
  clang-tools-extra/docs/ReleaseNotes.rst
  clang-tools-extra/docs/clang-tidy/checks/cuda/.keep
  clang-tools-extra/docs/clang-tidy/checks/cuda/unsafe-api-call.rst
  clang-tools-extra/docs/clang-tidy/checks/list.rst
  clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cuda/cuda.h
  clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cuda/cuda_runtime.h
  clang-tools-extra/test/clang-tidy/checkers/cuda/.keep
  
clang-tools-extra/test/clang-tidy/checkers/cuda/unsafe-api-call-function-handler.cu
  
clang-tools-extra/test/clang-tidy/checkers/cuda/unsafe-api-call-macro-handler.cu

Index: clang-tools-extra/test/clang-tidy/checkers/cuda/unsafe-api-call-macro-handler.cu
===================================================================
--- /dev/null
+++ 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 <cuda/cuda_runtime.h>
+
+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());
+}
Index: clang-tools-extra/test/clang-tidy/checkers/cuda/unsafe-api-call-function-handler.cu
===================================================================
--- /dev/null
+++ 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 <cuda/cuda_runtime.h>
+
+#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());
+}
Index: clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cuda/cuda_runtime.h
===================================================================
--- /dev/null
+++ clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cuda/cuda_runtime.h
@@ -0,0 +1,3 @@
+#include "cuda.h"
+
+cudaError_t cudaDeviceReset();
Index: clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cuda/cuda.h
===================================================================
--- clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cuda/cuda.h
+++ clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/cuda/cuda.h
@@ -14,7 +14,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);
Index: clang-tools-extra/docs/clang-tidy/checks/list.rst
===================================================================
--- clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -199,6 +199,7 @@
    `cppcoreguidelines-pro-type-vararg <cppcoreguidelines/pro-type-vararg.html>`_,
    `cppcoreguidelines-slicing <cppcoreguidelines/slicing.html>`_,
    `cppcoreguidelines-special-member-functions <cppcoreguidelines/special-member-functions.html>`_,
+   `cuda-unsafe-api-call <cuda/unsafe-api-call.html>`_, "Yes"
    `cppcoreguidelines-virtual-class-destructor <cppcoreguidelines/virtual-class-destructor.html>`_, "Yes"
    `darwin-avoid-spinlock <darwin/avoid-spinlock.html>`_,
    `darwin-dispatch-once-nonstatic <darwin/dispatch-once-nonstatic.html>`_, "Yes"
Index: clang-tools-extra/docs/clang-tidy/checks/cuda/unsafe-api-call.rst
===================================================================
--- /dev/null
+++ 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.
Index: clang-tools-extra/docs/ReleaseNotes.rst
===================================================================
--- clang-tools-extra/docs/ReleaseNotes.rst
+++ clang-tools-extra/docs/ReleaseNotes.rst
@@ -153,7 +153,8 @@
 
   The checks check for best practices in CUDA code, such as checking if a call to a kernel was
   executed correctly. They are also highly customizable and provide automatic fixes to the
-  projects that use them. For more information, see the documentation for...
+  projects that use them. For more information, see the documentation for
+  :doc:`cuda-unsafe-api-call <clang-tidy/checks/cuda/unsafe-api-call>` check
 
 Removed checks
 ^^^^^^^^^^^^^^
Index: clang-tools-extra/clang-tidy/cuda/UnsafeApiCallCheck.h
===================================================================
--- /dev/null
+++ clang-tools-extra/clang-tidy/cuda/UnsafeApiCallCheck.h
@@ -0,0 +1,109 @@
+//===--- 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
+//
+//===----------------------------------------------------------------------===//
+
+#pragma once
+
+#include "../ClangTidyCheck.h"
+#include "llvm/ADT/StringSet.h"
+#include <memory>
+#include <unordered_set>
+
+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<llvm::MallocAllocator> AcceptedHandlersSet;
+  // Generates AcceptedHandlersSet from AcceptedHandlersList
+  static llvm::StringSet<llvm::MallocAllocator>
+  splitAcceptedHandlers(const llvm::StringRef &AcceptedHandlers,
+                        const llvm::StringRef &HandlerName);
+  bool limitAcceptedHandlers();
+
+  // Only used when "AcceptedHandlers" is set
+  std::unordered_set<SourceLocation,
+                     std::function<unsigned(const SourceLocation &)>>
+      AcceptedHandlerMacroLocations;
+  std::unordered_set<const Stmt *> UnusedValueNodes;
+  std::unique_ptr<UnusedValueCallback> UnusedValueCallbackInstance;
+};
+
+} // namespace cuda
+} // namespace tidy
+} // namespace clang
Index: clang-tools-extra/clang-tidy/cuda/UnsafeApiCallCheck.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/clang-tidy/cuda/UnsafeApiCallCheck.cpp
@@ -0,0 +1,305 @@
+//===--- 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 <functional>
+#include <iostream>
+#include <sstream>
+
+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<llvm::MallocAllocator>
+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::MallocAllocator>();
+  }
+  llvm::SmallVector<llvm::StringRef> AcceptedHandlersVector;
+  AcceptedHandlers.split(AcceptedHandlersVector, ',');
+
+  llvm::StringSet<llvm::MallocAllocator> 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<PPCallbacks>(this));
+  }
+}
+
+namespace {
+
+// Check if the declaration is in a specific header based on a condition
+AST_MATCHER_P(Decl, isInSourceFile, std::function<bool(const StringRef &)>,
+              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<bool(const StringRef &)>,
+              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<bool(const StringRef &)>,
+              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<Stmt>(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<UnusedValueCallback>(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 =
+    "Unchecked CUDA API call. Consider wrapping it with a call to "
+    "an error handler:";
+constexpr auto NoHandlerMsg =
+    "Unchecked CUDA API call. 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 auto ApiCallNode = Result.Nodes.getNodeAs<Stmt>(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(), NoHandlerMsg);
+  } else if (isStmtInMacro(ApiCallNode)) {
+    diag(
+        ApiCallNode->getBeginLoc(),
+        (llvm::Twine(
+             "Unchecked CUDA API call. Consider wrapping it with a call to `") +
+         HandlerName + "`")
+            .str());
+  } else {
+    diag(ApiCallNode->getBeginLoc(), HandlerMsg)
+        << FixItHint::CreateReplacement(
+               ApiCallNode->getSourceRange(),
+               (HandlerName + "(" +
+                tooling::fixit::getText(ApiCallNode->getSourceRange(),
+                                        *Result.Context) +
+                ")")
+                   .str());
+  }
+}
+
+void UnsafeApiCallCheck::checkBadHandler(
+    const MatchFinder::MatchResult &Result) {
+  const auto ApiCallNode = Result.Nodes.getNodeAs<Stmt>(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(), NoHandlerMsg);
+  } else if (isStmtInMacro(ApiCallNode) ||
+             UnusedValueNodes.find(ApiCallNode) == UnusedValueNodes.end()) {
+    diag(
+        ApiCallNode->getBeginLoc(),
+        (llvm::Twine(
+             "Unchecked CUDA API call. Consider wrapping it with a call to `") +
+         HandlerName + "`")
+            .str());
+  } else {
+    diag(ApiCallNode->getBeginLoc(), HandlerMsg)
+        << FixItHint::CreateReplacement(
+               ApiCallNode->getSourceRange(),
+               (HandlerName + "(" +
+                tooling::fixit::getText(ApiCallNode->getSourceRange(),
+                                        *Result.Context) +
+                ")")
+                   .str());
+  }
+}
+
+} // namespace cuda
+} // namespace tidy
+} // namespace clang
Index: clang-tools-extra/clang-tidy/cuda/CudaTidyModule.cpp
===================================================================
--- clang-tools-extra/clang-tidy/cuda/CudaTidyModule.cpp
+++ clang-tools-extra/clang-tidy/cuda/CudaTidyModule.cpp
@@ -1,4 +1,4 @@
-//===--- GoogleTidyModule.cpp - clang-tidy --------------------------------===//
+//===--- CudaTidyModule.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.
@@ -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<UnsafeApiCallCheck>("cuda-unsafe-api-call");
+  }
 };
 
 // Register the CudaTidyModule using this statically initialized variable.
Index: clang-tools-extra/clang-tidy/cuda/CMakeLists.txt
===================================================================
--- clang-tools-extra/clang-tidy/cuda/CMakeLists.txt
+++ clang-tools-extra/clang-tidy/cuda/CMakeLists.txt
@@ -1,5 +1,6 @@
 add_clang_library(clangTidyCudaModule
   CudaTidyModule.cpp
+  UnsafeApiCallCheck.cpp
   LINK_LIBS
   clangTidy
   clangTidyUtils
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to