compnerd created this revision.
compnerd added a reviewer: aaron.ballman.
Herald added a subscriber: mgorny.
compnerd requested review of this revision.
Herald added a project: clang-tools-extra.

This introduces a new check, readability-containter-data-pointer.  This
check is meant to catch the cases where the user may be trying to
materialize the data pointer by taking the address of the 0-th member of
a container.  With C++11 or newer, the `data` member should be used for
this.  This provides the following benefits:

- `.data()` is easier to read than `&[0]`
- it avoids an unnecessary re-materialization of the pointer
  - this doesn't matter in the case of optimized code, but in the case of 
unoptimized code, this will be visible
- it avoids a potential invalid memory de-reference caused by the indexing when 
the container is empty (in debug mode, clang will normally optimize away the 
re-materialization in optimized builds).

The small potential behavioural change raises the question of where the
check should belong.  A reasoning of defense in depth applies here, and
this does an unchecked conversion, with the assumption that users can
use the static analyzer to catch cases where we can statically identify
an invalid memory de-reference.  For the cases where the static analysis
is unable to prove the size of the container, UBSan can be used to track
the invalid access.

Special thanks to Aaron Ballman for the discussion on whether this
check would be useful and where to place it.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D108893

Files:
  clang-tools-extra/clang-tidy/readability/CMakeLists.txt
  clang-tools-extra/clang-tidy/readability/ContainerDataPointerCheck.cpp
  clang-tools-extra/clang-tidy/readability/ContainerDataPointerCheck.h
  clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
  
clang-tools-extra/test/clang-tidy/checkers/readability-container-data-pointer.cpp

Index: clang-tools-extra/test/clang-tidy/checkers/readability-container-data-pointer.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/test/clang-tidy/checkers/readability-container-data-pointer.cpp
@@ -0,0 +1,104 @@
+// RUN: %check_clang_tidy %s readability-container-data-pointer %t -- -- -fno-delayed-template-parsing
+
+typedef __SIZE_TYPE__ size_t;
+
+namespace std {
+template <typename T>
+struct vector {
+  using size_type = size_t;
+
+  vector();
+  explicit vector(size_type);
+
+  T *data();
+  const T *data() const;
+
+  T &operator[](size_type);
+  const T &operator[](size_type) const;
+};
+
+template <typename T>
+struct basic_string {
+  using size_type = size_t;
+
+  basic_string();
+
+  T *data();
+  const T *data() const;
+
+  T &operator[](size_t);
+  const T &operator[](size_type) const;
+};
+
+typedef basic_string<char> string;
+typedef basic_string<wchar_t> wstring;
+
+template <typename T>
+struct is_integral;
+
+template <>
+struct is_integral<size_t> {
+  static const bool value = true;
+};
+
+template <bool, typename T = void>
+struct enable_if { };
+
+template <typename T>
+struct enable_if<true, T> {
+  typedef T type;
+};
+}
+
+template <typename T>
+void f(const T *);
+
+#define z (0)
+
+void g(size_t s) {
+  std::vector<unsigned char> b(s);
+  f(&((b)[(z)]));
+  // CHECK-MESSAGES: :[[@LINE-1]]: warning: 'data' should be used for accessing the data pointer instead of taking the address of the 0-th element [readability-container-data-pointer]
+  // CHECK-FIXES: {{^  }}f(b.data());{{$}}
+}
+
+void h() {
+  std::string s;
+  f(&((s).operator[]((z))));
+  // CHECK-MESSAGES: :[[@LINE-1]]: warning: 'data' should be used for accessing the data pointer instead of taking the address of the 0-th element [readability-container-data-pointer]
+  // CHECK-FIXES: {{^  }}f(s.data());{{$}}
+
+  std::wstring w;
+  f(&((&(w))->operator[]((z))));
+  // CHECK-MESSAGES: :[[@LINE-1]]: warning: 'data' should be used for accessing the data pointer instead of taking the address of the 0-th element [readability-container-data-pointer]
+  // CHECK-FIXES: {{^  }}f(w.data());{{$}}
+}
+
+template <typename T, typename U,
+          typename = typename std::enable_if<std::is_integral<U>::value>::type>
+void i(U s) {
+  std::vector<T> b(s);
+  f(&b[0]);
+  // CHECK-MESSAGES: :[[@LINE-1]]: warning: 'data' should be used for accessing the data pointer instead of taking the address of the 0-th element [readability-container-data-pointer]
+  // CHECK-FIXES: {{^  }}f(b.data());{{$}}
+}
+
+template void i<unsigned char, size_t>(size_t);
+
+void j(std::vector<unsigned char> * const v) {
+  f(&(*v)[0]);
+  // CHECK-MESSAGES: :[[@LINE-1]]: warning: 'data' should be used for accessing the data pointer instead of taking the address of the 0-th element [readability-container-data-pointer]
+  // CHECK-FIXES: {{^  }}f(v->data());{{$}}
+}
+
+void k(const std::vector<unsigned char> &v) {
+  f(&v[0]);
+  // CHECK-MESSAGES: :[[@LINE-1]]: warning: 'data' should be used for accessing the data pointer instead of taking the address of the 0-th element [readability-container-data-pointer]
+  // CHECK-FIXES: {{^  }}f(v.data());{{$}}
+}
+
+void l() {
+  unsigned char b[32];
+  f(&b[0]);
+  // CHECK-MESSAGES-NOT: :[[@LINE-1]]: warning: 'data' should be used for accessing the data pointer instead of taking the address of the 0-th element [readability-container-data-pointer]
+}
Index: clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
===================================================================
--- clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
+++ clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
@@ -12,6 +12,7 @@
 #include "AvoidConstParamsInDecls.h"
 #include "BracesAroundStatementsCheck.h"
 #include "ConstReturnTypeCheck.h"
+#include "ContainerDataPointerCheck.h"
 #include "ContainerSizeEmptyCheck.h"
 #include "ConvertMemberFunctionsToStatic.h"
 #include "DeleteNullPointerCheck.h"
@@ -62,6 +63,8 @@
         "readability-braces-around-statements");
     CheckFactories.registerCheck<ConstReturnTypeCheck>(
         "readability-const-return-type");
+    CheckFactories.registerCheck<ContainerDataPointerCheck>(
+        "readability-container-data-pointer");
     CheckFactories.registerCheck<ContainerSizeEmptyCheck>(
         "readability-container-size-empty");
     CheckFactories.registerCheck<ConvertMemberFunctionsToStatic>(
Index: clang-tools-extra/clang-tidy/readability/ContainerDataPointerCheck.h
===================================================================
--- /dev/null
+++ clang-tools-extra/clang-tidy/readability/ContainerDataPointerCheck.h
@@ -0,0 +1,45 @@
+//===--- ContainerDataPointerCheck.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_READABILITY_CONTAINERDATAPOINTERCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_CONTAINERDATAPOINTERCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang {
+namespace tidy {
+namespace readability {
+/// Checks whether a call to `operator[]` and `&` can be replaced with a call to
+/// `data()`.
+///
+/// This only replaces the case where the offset being accessed through the
+/// subscript operation is a known constant 0.  This avoids a potential invalid
+/// memory access when the container is empty.  Cases where the constant is not
+/// explictly zero can be addressed through teh clang static analyzer, and those
+/// which cannot be statically identified can be caught using UBSan.
+class ContainerDataPointerCheck : public ClangTidyCheck {
+public:
+  ContainerDataPointerCheck(StringRef Name, ClangTidyContext *Context);
+
+  bool isLanguageVersionSupported(const LangOptions &LO) const override {
+    return LO.CPlusPlus11;
+  }
+
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+
+  llvm::Optional<TraversalKind> getCheckTraversalKind() const override {
+    return TK_IgnoreUnlessSpelledInSource;
+  }
+};
+} // namespace readability
+} // namespace tidy
+} // namespace clang
+
+#endif
Index: clang-tools-extra/clang-tidy/readability/ContainerDataPointerCheck.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/clang-tidy/readability/ContainerDataPointerCheck.cpp
@@ -0,0 +1,92 @@
+//===--- ContainerDataPointerCheck.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 "ContainerDataPointerCheck.h"
+
+#include "clang/Lex/Lexer.h"
+#include "llvm/ADT/StringRef.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang {
+namespace tidy {
+namespace readability {
+ContainerDataPointerCheck::ContainerDataPointerCheck(StringRef Name,
+                                                     ClangTidyContext *Context)
+    : ClangTidyCheck(Name, Context) {}
+
+void ContainerDataPointerCheck::registerMatchers(MatchFinder *Finder) {
+  const auto Record =
+      cxxRecordDecl(isSameOrDerivedFrom(namedDecl(has(cxxMethodDecl(isPublic(),
+                                                                    hasName("data"))
+                                                          .bind("data")))
+                                            .bind("container")))
+          .bind("record");
+
+  const auto NonTemplateContainerType =
+      qualType(hasUnqualifiedDesugaredType(recordType(hasDeclaration(Record))));
+  const auto TemplateContainerType =
+      qualType(hasUnqualifiedDesugaredType(templateSpecializationType(hasDeclaration(classTemplateDecl(has(Record))))));
+
+  const auto Container =
+      qualType(anyOf(NonTemplateContainerType, TemplateContainerType));
+
+  Finder->addMatcher(unaryOperator(unless(isExpansionInSystemHeader()),
+                                   hasOperatorName("&"),
+                                   hasUnaryOperand(anyOf(ignoringParenImpCasts(cxxOperatorCallExpr(callee(cxxMethodDecl(hasName("operator[]"))
+                                                                                                              .bind("operator[]")),
+                                                                                                  argumentCountIs(2),
+                                                                                                  hasArgument(0, anyOf(ignoringParenImpCasts(declRefExpr(to(varDecl(anyOf(hasType(Container),
+                                                                                                                                                                          hasType(pointsTo(Container)),
+                                                                                                                                                                          hasType(references(Container))))))
+                                                                                                                                                .bind("var")),
+                                                                                                                       ignoringParenImpCasts(hasDescendant(declRefExpr(to(varDecl(anyOf(hasType(Container),
+                                                                                                                                                                                        hasType(pointsTo(Container)),
+                                                                                                                                                                                        hasType(references(Container))))))
+                                                                                                                                                              .bind("var"))))),
+                                                                                                  hasArgument(1, ignoringParenImpCasts(integerLiteral(equals(0))
+                                                                                                                                            .bind("zero"))))
+                                                                                  .bind("operator-call")),
+                                                         ignoringParenImpCasts(cxxMemberCallExpr(hasDescendant(declRefExpr(to(varDecl(anyOf(hasType(Container),
+                                                                                                                                            hasType(pointsTo(Container)),
+                                                                                                                                            hasType(references(Container))))))
+                                                                                                                  .bind("var")),
+                                                                                                 argumentCountIs(1),
+                                                                                                 hasArgument(0, ignoringParenImpCasts(integerLiteral(equals(0))
+                                                                                                                                          .bind("zero"))))
+                                                                                  .bind("member-call")),
+                                                         ignoringParenImpCasts(arraySubscriptExpr(hasLHS(ignoringParenImpCasts(declRefExpr(to(varDecl(anyOf(hasType(Container),
+                                                                                                                                                            hasType(pointsTo(Container)),
+                                                                                                                                                            hasType(references(Container))))))
+                                                                                                                                  .bind("var"))),
+                                                                                                  hasRHS(ignoringParenImpCasts(integerLiteral(equals(0))
+                                                                                                                                  .bind("zero"))))
+                                                                                  .bind("array-subscript")))))
+                        .bind("address-of"), this);
+}
+
+void ContainerDataPointerCheck::check(const MatchFinder::MatchResult &Result) {
+  const auto *UO = Result.Nodes.getNodeAs<UnaryOperator>("address-of");
+  const auto *DRE = Result.Nodes.getNodeAs<DeclRefExpr>("var");
+
+  std::string ReplacementText;
+  ReplacementText =
+      std::string(Lexer::getSourceText(CharSourceRange::getTokenRange(DRE->getSourceRange()),
+                                       *Result.SourceManager, getLangOpts()));
+  if (DRE->getType()->isPointerType())
+    ReplacementText += "->data()";
+  else
+    ReplacementText += ".data()";
+
+  FixItHint Hint = FixItHint::CreateReplacement(UO->getSourceRange(), ReplacementText);
+  diag(UO->getBeginLoc(), "'data' should be used for accessing the data pointer instead of taking the address of the 0-th element")
+    << Hint;
+}
+} // namespace readability
+} // namespace tidy
+} // namespace clang
Index: clang-tools-extra/clang-tidy/readability/CMakeLists.txt
===================================================================
--- clang-tools-extra/clang-tidy/readability/CMakeLists.txt
+++ clang-tools-extra/clang-tidy/readability/CMakeLists.txt
@@ -7,6 +7,7 @@
   AvoidConstParamsInDecls.cpp
   BracesAroundStatementsCheck.cpp
   ConstReturnTypeCheck.cpp
+  ContainerDataPointerCheck.cpp
   ContainerSizeEmptyCheck.cpp
   ConvertMemberFunctionsToStatic.cpp
   DeleteNullPointerCheck.cpp
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to