ccotter updated this revision to Diff 546991.
ccotter marked an inline comment as done.
ccotter added a comment.

rebase


Repository:
  rG LLVM Github Monorepo

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

https://reviews.llvm.org/D145138

Files:
  clang-tools-extra/clang-tidy/modernize/AvoidCArraysCheck.cpp
  clang-tools-extra/clang-tidy/modernize/AvoidCArraysCheck.h
  clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp
  clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.h
  clang-tools-extra/clang-tidy/utils/CMakeLists.txt
  clang-tools-extra/clang-tidy/utils/LexerUtils.cpp
  clang-tools-extra/clang-tidy/utils/TypeUtils.cpp
  clang-tools-extra/clang-tidy/utils/TypeUtils.h
  clang-tools-extra/docs/ReleaseNotes.rst
  clang-tools-extra/docs/clang-tidy/checks/modernize/avoid-c-arrays.rst
  clang-tools-extra/test/clang-tidy/checkers/modernize/avoid-c-arrays-cxx17.cpp
  clang-tools-extra/test/clang-tidy/checkers/modernize/avoid-c-arrays.cpp

Index: clang-tools-extra/test/clang-tidy/checkers/modernize/avoid-c-arrays.cpp
===================================================================
--- clang-tools-extra/test/clang-tidy/checkers/modernize/avoid-c-arrays.cpp
+++ clang-tools-extra/test/clang-tidy/checkers/modernize/avoid-c-arrays.cpp
@@ -1,4 +1,6 @@
-// RUN: %check_clang_tidy %s modernize-avoid-c-arrays %t
+// RUN: %check_clang_tidy -std=c++11 %s modernize-avoid-c-arrays %t
+
+//CHECK-FIXES: #include <array>
 
 int a[] = {1, 2};
 // CHECK-MESSAGES: :[[@LINE-1]]:1: warning: do not declare C-style arrays, use std::array<> instead
@@ -86,3 +88,280 @@
   int j[1];
 };
 }
+
+template <class T> struct TStruct {};
+
+void replacements() {
+  int ar[10];
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<int, 10> ar;
+  TStruct<int> ar2[10];
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<TStruct<int>, 10> ar2;
+  TStruct< int > ar3[10];
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<TStruct< int >, 10> ar3;
+  int * ar4[10];
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<int *, 10> ar4;
+  int * /*comment*/ar5[10];
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<int *, 10> /*comment*/ar5;
+  volatile const int * ar6[10];
+  // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<volatile const int *, 10> ar6;
+  volatile int ar7[10];
+  // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: volatile std::array<int, 10> ar7;
+  int const * ar8[10];
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<int const *, 10> ar8;
+  int ar9[1];
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<int, 1> ar9;
+  static int volatile constexpr ar10[10] = {};
+  // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: static volatile constexpr std::array<int, 10> ar10 = {{[{][{]}}{{[}][}]}};
+  thread_local int ar11[10];
+  // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: thread_local std::array<int, 10> ar11;
+  thread_local/*a*/int/*b*/ar12[10];
+  // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: thread_local/*a*/std::array<int, 10>/*b*/ar12;
+  /*a*/ int/*b*/ /*c*/*/*d*/ /*e*/ /*f*/ar13[10];
+  // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: /*a*/ std::array<int/*b*/ /*c*/*, 10>/*d*/ /*e*/ /*f*/ar13;
+  TStruct<int*> ar14[10];
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<TStruct<int*>, 10> ar14;
+  volatile TStruct<const int*> ar15[10];
+  // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: volatile std::array<TStruct<const int*>, 10> ar15;
+  TStruct<int const*> ar16[10];
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<TStruct<int const*>, 10> ar16;
+  TStruct<unsigned const int> ar17[10];
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<TStruct<unsigned const int>, 10> ar17;
+  volatile int static thread_local * ar18[10];
+  // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: static thread_local std::array<volatile int *, 10> ar18;
+
+  // Note, there is a tab '\t' before the semicolon in the declaration below.
+  int ar19[3]	;
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<int, 3> ar19	;
+
+  int
+  ar20[3];
+  // CHECK-MESSAGES: :[[@LINE-2]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<int, 3>
+  // CHECK-FIXES-NEXT: ar20;
+
+  int init[] = {1,2,3};
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<int, 3> init = {{[{][{]}}1,2,3{{[}][}]}};
+  int init2[3] = {1,2,3};
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<int, 3> init2 = {{[{][{]}}1,2,3{{[}][}]}};
+  int init3[3] = {1,2};
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<int, 3> init3 = {{[{][{]}}1,2{{[}][}]}};
+  char init4[] = "abcdef";
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<char, 7> init4 = {"abcdef"};
+  wchar_t init5[] = L"abcdef";
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<wchar_t, 7> init5 = {L"abcdef"};
+  char init6[] = "";
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<char, 1> init6 = {""};
+  char init7[] = "\0";
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<char, 2> init7 = {"\0"};
+  char init8[] = {"abc"};
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<char, 4> init8 = {{[{][{]}}"abc"{{[}][}]}};
+  char init9[] = R"LONG(a
+  really
+  long
+  string)LONG";
+  // CHECK-MESSAGES: :[[@LINE-4]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<char, 27> init9 = {R"LONG(a
+  // CHECK-FIXES-NEXT:   really
+  // CHECK-FIXES-NEXT:   long
+  // CHECK-FIXES-NEXT:   string)LONG"};
+  char init10[] = {"abc" "def"};
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<char, 7> init10 = {{[{][{]}}"abc" "def"{{[}][}]}};
+  const char* init11 = {nullptr};
+  const char* const init12[] = {"abc","def"};
+  // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: const std::array<const char*, 2> init12 = {{[{][{]}}"abc","def"{{[}][}]}};
+  const char*const init13[] = {"abc","def"};
+  // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: const std::array<const char*, 2>init13 = {{[{][{]}}"abc","def"{{[}][}]}};
+
+  // CHECK-MESSAGES: :[[@LINE+2]]:9: warning: do not declare C-style arrays, use std::array<> instead
+#define NAME "ABC"
+  const char init14[] = NAME
+#if 1
+    " "
+#endif
+    ;
+
+  // Note: there are two tab '\t' characters between the 'int', '*', and 'init15' tokens below.
+  int	*	init15[1] = {nullptr};
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<int	*, 1>	init15 = {{[{][{]}}nullptr{{[}][}]}};
+
+  int two_d[10][5];
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: int two_d[10][5];
+  int three_d[10][5][3];
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: int three_d[10][5][3];
+}
+
+void replace_cv_within_type() {
+  // FIXME: clang's AST TypeLoc etc do not give SourceLocations of individual
+  // qualifiers etc. In combined types (e.g. 'unsigned int') with a cv
+  // qualifier in the middle of the type (e.g., 'unsigned volatile int'), we
+  // do not support pulling the qualifier out to appear before the std::array
+  // decl.
+
+  unsigned volatile int ar1[10];
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: unsigned volatile int ar1[10];
+  volatile unsigned int ar2[10];
+  // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: volatile std::array<unsigned int, 10> ar2;
+  unsigned int volatile ar3[10];
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: volatile std::array<unsigned int, 10> ar3;
+  unsigned const int* volatile ar4[10];
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: volatile std::array<unsigned const int*, 10> ar4;
+}
+
+void consumes_ptr(int*);
+void consumes_ptr(char*);
+void replacement_with_refs() {
+  int ar[10];
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<int, 10> ar;
+
+  for (int i = 0; i != sizeof(ar)/sizeof(ar[0]); ++i) { ar[i]++; }
+  // CHECK-FIXES: for (int i = 0; i != sizeof(ar)/sizeof(ar[0]); ++i) { ar[i]++; }
+  for (int i: ar) {}
+  // CHECK-FIXES: for (int i: ar) {}
+
+  int* ptr = ar;
+  // CHECK-FIXES: int* ptr = ar.begin();
+  const int* cptr = ar;
+  // CHECK-FIXES: const int* cptr = ar.begin();
+  ptr = (ar+1);
+  // CHECK-FIXES: ptr = (ar.begin()+1);
+
+  int a;
+  a = ar[0] + (ar)[1] + ((ar))[2];
+  // CHECK-FIXES: a = ar[0] + (ar)[1] + ((ar))[2];
+  a = 0[ar] + (0)[ar] + 0[(ar)];
+  // CHECK-FIXES: a = ar[0] + ar[(0)] + (ar)[0];
+  a = *(ar+2);
+  // CHECK-FIXES: a = *(ar.begin()+2);
+  consumes_ptr(ar);
+  // CHECK-FIXES: consumes_ptr(ar.begin());
+
+  unsigned sz = sizeof(ar) + sizeof((ar)) + sizeof(ar[0]) + sizeof ar;
+  // CHECK-FIXES: unsigned sz = sizeof(ar) + sizeof((ar)) + sizeof(ar[0]) + sizeof ar;
+}
+
+void replacement_with_refs_string_array() {
+  char str[] = {"abcdef"};
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<char, 7> str = {{[{][{]}}"abcdef"{{[}][}]}};
+
+  consumes_ptr(str);
+  // CHECK-FIXES: consumes_ptr(str.begin());
+
+  char* cp;
+  cp = str + 0;
+  // CHECK-FIXES: cp = str.begin() + 0;
+  cp = str + 1;
+  // CHECK-FIXES: cp = str.begin() + 1;
+  cp = &str[0];
+  // CHECK-FIXES: cp = &str[0];
+}
+
+void cannot_replace_reference_taken() {
+  int ar[10];
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  int (&ref)[10] = ar;
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+}
+
+struct SomeClass {
+  void fn1(int);
+  void fn2(int);
+};
+void fn1(int);
+void fn2(int);
+void cannot_replace_array_of_function_pointers() {
+  void (*fns1[])(int) = {fn1, fn2};
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+
+  using FnPtr = void(*)(int);
+  FnPtr fns2[] = {fn1, fn2};
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+
+  void (SomeClass::*fns3[])(int) = {&SomeClass::fn1, &SomeClass::fn2};
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+
+  using MemFnPtr = void(SomeClass::*)(int);
+  MemFnPtr fns4[] = {&SomeClass::fn1, &SomeClass::fn2};
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+}
+
+void consume_array_ref(int (&)[10]);
+// CHECK-MESSAGES: :[[@LINE-1]]:24: warning: do not declare C-style arrays, use std::array<> instead
+void cannot_replace_reference_taken_in_call() {
+  int ar[10];
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  consume_array_ref(ar);
+}
+
+void vla_not_replaced(int n) {
+  int ar[n];
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C VLA arrays, use std::vector<> instead
+}
+
+struct Obj1 {};
+struct Obj2 {
+  Obj2(const Obj1& = {});
+};
+void init_expr_with_temp() {
+  Obj2 ar[] = { {} };
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<Obj2, 1> ar = {{[{][{]}} {} {{[}][}]}};
+}
+
+struct AStruct {
+  #define DEFINES_METHOD_WITH_ARRAY void method() { int ar[10]; }
+  DEFINES_METHOD_WITH_ARRAY
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+};
+
+void macros_not_replaced() {
+  int d[3];
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+#define EXPANDS_X(x) x
+  EXPANDS_X(consumes_ptr(d));
+}
+
+void attrs_not_replaced() {
+  [[maybe_unused]] int ar1[10];
+  // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: do not declare C-style arrays, use std::array<> instead
+  __attribute__((__unused__)) int ar2[10];
+  // CHECK-MESSAGES: :[[@LINE-1]]:31: warning: do not declare C-style arrays, use std::array<> instead
+}
Index: clang-tools-extra/test/clang-tidy/checkers/modernize/avoid-c-arrays-cxx17.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/test/clang-tidy/checkers/modernize/avoid-c-arrays-cxx17.cpp
@@ -0,0 +1,131 @@
+// RUN: %check_clang_tidy -std=c++17 %s modernize-avoid-c-arrays %t
+
+//CHECK-FIXES: #include <array>
+
+template <class T1, class T2>
+struct Pair {
+  T1 t1;
+  T2 t2;
+};
+
+struct Obj1 {};
+struct Obj2 {
+  Obj2(const Obj1& = {});
+};
+
+struct StringRef {
+  StringRef(const char*);
+  StringRef(const StringRef&);
+};
+
+void ctad_replacements() {
+  const int ci1{}, ci2{};
+  int i1, i2;
+
+  int ar[10];
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<int, 10> ar;
+
+  int init[] = {1,2,3};
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array init = {1,2,3};
+  int init2[3] = {1,2,3};
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<int, 3> init2 = { {1,2,3} };
+  char init3[] = "abcdef";
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<char, 7> init3 = { "abcdef" };
+  char init4[] = "";
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<char, 1> init4 = { "" };
+  char init5[] = {"abc"};
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<char, 4> init5 = { {"abc"} };
+  char init6[] = {"abc" "def"};
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<char, 7> init6 = { {"abc" "def"} };
+  int init7[] = {'c',2,3};
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<int, 3> init7 = { {'c',2,3} };
+  const int init8[] = {1,2,3};
+  // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: const std::array init8 = {1,2,3};
+  int const init9[] = {1,2,3};
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: const std::array init9 = {1,2,3};
+  int const init10[] = {i1, i2};
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: const std::array init10 = {i1, i2};
+  int const init11[] = {ci1, ci2};
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: const std::array init11 = {ci1, ci2};
+  int init12[] = {ci1, ci2};
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array init12 = {ci1, ci2};
+  constexpr volatile int static const init13[] = {1,2,3};
+  // CHECK-MESSAGES: :[[@LINE-1]]:22: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: constexpr volatile static const std::array init13 = {1,2,3};
+  const Obj1 init14[] = {Obj1(), Obj1()};
+  // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: const std::array init14 = {Obj1(), Obj1()};
+
+  using IntPair = Pair<int,int>;
+  IntPair init15[] = {IntPair{1,2}, IntPair{3,4}};
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array init15 = {IntPair{1,2}, IntPair{3,4}};
+  using IntPair = Pair<int,int>;
+  IntPair init16[] = { {1,2}, {3,4} };
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<IntPair, 2> init16 = { { {1,2}, {3,4} } };
+
+  StringRef init17[] = {"a", "b"};
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<StringRef, 2> init17 = { {"a", "b"} };
+  StringRef sr1(""),sr2("");
+  StringRef init18[] = {sr1, sr2};
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array init18 = {sr1, sr2};
+  StringRef init19[] = {StringRef(""), StringRef("")};
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array init19 = {StringRef(""), StringRef("")};
+  StringRef init20[] = { {""} };
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array<StringRef, 1> init20 = { { {""} } };
+  StringRef const init21[] = { StringRef{""} };
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: const std::array init21 = { StringRef{""} };
+
+  int x0,x1;
+  int* init22[] = {&x0, &x1};
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array init22 = {&x0, &x1};
+  int*init23[] = {&x0, &x1};
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: std::array init23 = {&x0, &x1};
+  static thread_local int* const init24[] = {&x0, &x1};
+  // CHECK-MESSAGES: :[[@LINE-1]]:23: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: static thread_local const std::array init24 = {&x0, &x1};
+}
+
+void replace_cv_within_type() {
+  // FIXME: clang's AST TypeLoc etc do not give SourceLocations of individual
+  // qualifiers etc. In combined types (e.g. 'unsigned int') with a cv
+  // qualifier in the middle of the type (e.g., 'unsigned volatile int'), we
+  // do not support pulling the qualifier out to appear before the std::array
+  // decl.
+
+  unsigned const int ui1{}, ui2{};
+
+  unsigned volatile int ar1[] = {1u,2u};
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: unsigned volatile int ar1[] = {1u,2u};
+  volatile unsigned int ar2[] = {1u,2u};
+  // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: volatile std::array ar2 = {1u,2u};
+  unsigned int volatile ar3[] = {1u,2u};
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: volatile std::array ar3 = {1u,2u};
+  unsigned const int* volatile ar4[] = {&ui1, &ui2};
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not declare C-style arrays, use std::array<> instead
+  // CHECK-FIXES: volatile std::array ar4 = {&ui1, &ui2};
+}
Index: clang-tools-extra/docs/clang-tidy/checks/modernize/avoid-c-arrays.rst
===================================================================
--- clang-tools-extra/docs/clang-tidy/checks/modernize/avoid-c-arrays.rst
+++ clang-tools-extra/docs/clang-tidy/checks/modernize/avoid-c-arrays.rst
@@ -10,8 +10,8 @@
 Finds C-style array types and recommend to use ``std::array<>`` /
 ``std::vector<>``. All types of C arrays are diagnosed.
 
-However, fix-it are potentially dangerous in header files and are therefore not
-emitted right now.
+Fix-its are generated for C-style arrays in function bodies. Fix-its are not
+provided in other contexts (e.g., class member variables).
 
 .. code:: c++
 
@@ -34,6 +34,17 @@
 
   using k = int[4]; // warning: do not declare C-style arrays, use std::array<> instead
 
+  void somefunction() {
+    int a[10]; // warning: do not declare C-style arrays, use std::array<> instead
+               // replaced with 'std::array<int, 10> a'
+    int v = a[0];
+    int* ptr = a; // replaced with 'int* ptr = a.begin()'
+
+    int a2[] = {1,2,3}; // warning: do not declare C-style arrays, use std::array<> instead
+                        // In C++14 and older, replaced with 'std::array<int, 3> a2 = {{1,2,3}}'
+                        // In C++17 and newer, replaced with 'std::array a2 = {1,2,3}'
+  }
+
 
 However, the ``extern "C"`` code is ignored, since it is common to share
 such headers between C code, and C++ code.
@@ -58,3 +69,11 @@
 Similarly, the ``main()`` function is ignored. Its second and third parameters
 can be either ``char* argv[]`` or ``char** argv``, but cannot be
 ``std::array<>``.
+
+Options
+-------
+
+.. option:: IncludeStyle
+
+   A string specifying which include-style is used, `llvm` or `google`. Default
+   is `llvm`.
Index: clang-tools-extra/docs/ReleaseNotes.rst
===================================================================
--- clang-tools-extra/docs/ReleaseNotes.rst
+++ clang-tools-extra/docs/ReleaseNotes.rst
@@ -134,6 +134,10 @@
 Changes in existing checks
 ^^^^^^^^^^^^^^^^^^^^^^^^^^
 
+- Improved :doc: `modernize-avoid-c-arrays
+  <clang-tidy/checks/modernize/avoid-c-arrays>` check to provide fix-its for
+  C-style arrays declared in function bodies.
+
 Removed checks
 ^^^^^^^^^^^^^^
 
Index: clang-tools-extra/clang-tidy/utils/TypeUtils.h
===================================================================
--- /dev/null
+++ clang-tools-extra/clang-tidy/utils/TypeUtils.h
@@ -0,0 +1,42 @@
+//===--- TypeUtils.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_UTILS_TYPE_UTILS_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_TYPE_UTILS_H
+
+#include "clang/Basic/SourceLocation.h"
+#include "clang/Lex/Token.h"
+#include "llvm/ADT/SmallVector.h"
+#include <optional>
+
+namespace clang {
+
+class ASTContext;
+class Decl;
+class Preprocessor;
+
+namespace tidy::utils::type {
+
+struct ClassifiedToken {
+  Token T;
+  bool IsQualifier;
+  bool IsSpecifier;
+};
+
+// Classify the qualifiers and specifier tokens of a declaration.
+std::optional<SmallVector<ClassifiedToken, 8>>
+classifyDeclTypeTokens(Preprocessor &PP, SourceLocation BeginLoc,
+                       SourceLocation EndLoc, const ASTContext &Ctx);
+
+std::optional<SmallVector<ClassifiedToken, 8>>
+classifyDeclTypeTokens(Preprocessor &PP, const Decl &D, const ASTContext &Ctx);
+
+} // namespace tidy::utils::type
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_TYPE_UTILS_H
Index: clang-tools-extra/clang-tidy/utils/TypeUtils.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/clang-tidy/utils/TypeUtils.cpp
@@ -0,0 +1,124 @@
+//===--- TypeUtils.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 "TypeUtils.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/Lex/Lexer.h"
+#include "clang/Lex/Preprocessor.h"
+#include <optional>
+
+namespace clang::tidy::utils::type {
+
+static bool isCvr(Token T) {
+  return T.isOneOf(tok::kw_const, tok::kw_volatile, tok::kw_restrict);
+}
+
+static bool isSpecifier(Token T) {
+  return T.isOneOf(tok::kw_constexpr, tok::kw_inline, tok::kw_extern,
+                   tok::kw_static, tok::kw_friend, tok::kw_virtual,
+                   tok::kw_thread_local, tok::kw_register);
+}
+
+static std::optional<ClassifiedToken> classifyToken(Preprocessor &PP,
+                                                    Token Tok) {
+  ClassifiedToken CT;
+  CT.T = Tok;
+  CT.IsQualifier = true;
+  CT.IsSpecifier = true;
+  bool ContainsQualifiers = false;
+  bool ContainsSpecifiers = false;
+  bool ContainsSomethingElse = false;
+
+  Token End;
+  End.startToken();
+  End.setKind(tok::eof);
+  SmallVector<Token, 2> Stream{Tok, End};
+
+  // FIXME: do not report these token to Preprocessor.TokenWatcher.
+  PP.EnterTokenStream(Stream, false, /*IsReinject=*/false);
+  while (true) {
+    Token T;
+    PP.Lex(T);
+    if (T.is(tok::eof))
+      break;
+
+    const bool Qual = isCvr(T);
+    const bool Spec = isSpecifier(T);
+    CT.IsQualifier &= Qual;
+    CT.IsSpecifier &= Spec;
+    ContainsQualifiers |= Qual;
+    ContainsSpecifiers |= Spec;
+    ContainsSomethingElse |= !Qual && !Spec;
+  }
+
+  // If the Token/Macro contains more than one type of tokens, we would need
+  // to split the macro in order to move parts to the trailing return type.
+  if (ContainsQualifiers + ContainsSpecifiers + ContainsSomethingElse > 1)
+    return std::nullopt;
+
+  return CT;
+}
+
+static SourceLocation expandIfMacroId(SourceLocation Loc,
+                                      const SourceManager &SM) {
+  if (Loc.isMacroID())
+    Loc = expandIfMacroId(SM.getImmediateExpansionRange(Loc).getBegin(), SM);
+  assert(!Loc.isMacroID() &&
+         "SourceLocation must not be a macro ID after recursive expansion");
+  return Loc;
+}
+
+std::optional<SmallVector<ClassifiedToken, 8>>
+classifyDeclTypeTokens(Preprocessor &PP, SourceLocation BeginLoc,
+                       SourceLocation EndLoc, const ASTContext &Ctx) {
+  const SourceManager &SM = Ctx.getSourceManager();
+  const LangOptions &LangOpts = Ctx.getLangOpts();
+  BeginLoc = expandIfMacroId(BeginLoc, SM);
+  EndLoc = expandIfMacroId(EndLoc, SM);
+
+  // Create tokens for everything before the name of the function.
+  std::pair<FileID, unsigned> Loc = SM.getDecomposedLoc(BeginLoc);
+  StringRef File = SM.getBufferData(Loc.first);
+  const char *TokenBegin = File.data() + Loc.second;
+  Lexer Lexer(SM.getLocForStartOfFile(Loc.first), LangOpts, File.begin(),
+              TokenBegin, File.end());
+  Token T;
+  SmallVector<ClassifiedToken, 8> ClassifiedTokens;
+  while (!Lexer.LexFromRawLexer(T) &&
+         SM.isBeforeInTranslationUnit(T.getLocation(), EndLoc)) {
+    if (T.is(tok::raw_identifier)) {
+      IdentifierInfo &Info = Ctx.Idents.get(
+          StringRef(SM.getCharacterData(T.getLocation()), T.getLength()));
+
+      if (Info.hasMacroDefinition()) {
+        const MacroInfo *MI = PP.getMacroInfo(&Info);
+        if (!MI || MI->isFunctionLike()) {
+          // Cannot handle function style macros.
+          return std::nullopt;
+        }
+      }
+
+      T.setIdentifierInfo(&Info);
+      T.setKind(Info.getTokenID());
+    }
+
+    if (std::optional<ClassifiedToken> CT = classifyToken(PP, T))
+      ClassifiedTokens.push_back(*CT);
+    else
+      return std::nullopt;
+  }
+
+  return ClassifiedTokens;
+}
+
+std::optional<SmallVector<ClassifiedToken, 8>>
+classifyDeclTypeTokens(Preprocessor &PP, const Decl &D, const ASTContext &Ctx) {
+  return classifyDeclTypeTokens(PP, D.getBeginLoc(), D.getLocation(), Ctx);
+}
+
+} // namespace clang::tidy::utils::type
Index: clang-tools-extra/clang-tidy/utils/LexerUtils.cpp
===================================================================
--- clang-tools-extra/clang-tidy/utils/LexerUtils.cpp
+++ clang-tools-extra/clang-tidy/utils/LexerUtils.cpp
@@ -120,6 +120,7 @@
   SourceLocation Loc = Range.getBegin();
 
   while (Loc <= Range.getEnd()) {
+  //while (SM.isBeforeInTranslationUnit(Loc, Range.getEnd())) {
     if (Loc.isMacroID())
       return true;
 
Index: clang-tools-extra/clang-tidy/utils/CMakeLists.txt
===================================================================
--- clang-tools-extra/clang-tidy/utils/CMakeLists.txt
+++ clang-tools-extra/clang-tidy/utils/CMakeLists.txt
@@ -22,6 +22,7 @@
   RenamerClangTidyCheck.cpp
   TransformerClangTidyCheck.cpp
   TypeTraits.cpp
+  TypeUtils.cpp
   UsingInserter.cpp
 
   LINK_LIBS
Index: clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.h
===================================================================
--- clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.h
+++ clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.h
@@ -15,12 +15,6 @@
 
 namespace clang::tidy::modernize {
 
-struct ClassifiedToken {
-  Token T;
-  bool IsQualifier;
-  bool IsSpecifier;
-};
-
 /// Rewrites function signatures to use a trailing return type.
 ///
 /// For the user-facing documentation see:
@@ -43,10 +37,6 @@
   SourceLocation findTrailingReturnTypeSourceLocation(
       const FunctionDecl &F, const FunctionTypeLoc &FTL, const ASTContext &Ctx,
       const SourceManager &SM, const LangOptions &LangOpts);
-  std::optional<SmallVector<ClassifiedToken, 8>>
-  classifyTokensBeforeFunctionName(const FunctionDecl &F, const ASTContext &Ctx,
-                                   const SourceManager &SM,
-                                   const LangOptions &LangOpts);
   SourceRange findReturnTypeAndCVSourceRange(const FunctionDecl &F,
                                              const TypeLoc &ReturnLoc,
                                              const ASTContext &Ctx,
Index: clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp
===================================================================
--- clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp
+++ clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "UseTrailingReturnTypeCheck.h"
+#include "../utils/TypeUtils.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/RecursiveASTVisitor.h"
 #include "clang/ASTMatchers/ASTMatchFinder.h"
@@ -20,6 +21,9 @@
 using namespace clang::ast_matchers;
 
 namespace clang::tidy::modernize {
+
+using utils::type::ClassifiedToken;
+
 namespace {
 struct UnqualNameVisitor : public RecursiveASTVisitor<UnqualNameVisitor> {
 public:
@@ -167,100 +171,6 @@
   return Result;
 }
 
-static bool isCvr(Token T) {
-  return T.isOneOf(tok::kw_const, tok::kw_volatile, tok::kw_restrict);
-}
-
-static bool isSpecifier(Token T) {
-  return T.isOneOf(tok::kw_constexpr, tok::kw_inline, tok::kw_extern,
-                   tok::kw_static, tok::kw_friend, tok::kw_virtual);
-}
-
-static std::optional<ClassifiedToken>
-classifyToken(const FunctionDecl &F, Preprocessor &PP, Token Tok) {
-  ClassifiedToken CT;
-  CT.T = Tok;
-  CT.IsQualifier = true;
-  CT.IsSpecifier = true;
-  bool ContainsQualifiers = false;
-  bool ContainsSpecifiers = false;
-  bool ContainsSomethingElse = false;
-
-  Token End;
-  End.startToken();
-  End.setKind(tok::eof);
-  SmallVector<Token, 2> Stream{Tok, End};
-
-  // FIXME: do not report these token to Preprocessor.TokenWatcher.
-  PP.EnterTokenStream(Stream, false, /*IsReinject=*/false);
-  while (true) {
-    Token T;
-    PP.Lex(T);
-    if (T.is(tok::eof))
-      break;
-
-    bool Qual = isCvr(T);
-    bool Spec = isSpecifier(T);
-    CT.IsQualifier &= Qual;
-    CT.IsSpecifier &= Spec;
-    ContainsQualifiers |= Qual;
-    ContainsSpecifiers |= Spec;
-    ContainsSomethingElse |= !Qual && !Spec;
-  }
-
-  // If the Token/Macro contains more than one type of tokens, we would need
-  // to split the macro in order to move parts to the trailing return type.
-  if (ContainsQualifiers + ContainsSpecifiers + ContainsSomethingElse > 1)
-    return std::nullopt;
-
-  return CT;
-}
-
-std::optional<SmallVector<ClassifiedToken, 8>>
-UseTrailingReturnTypeCheck::classifyTokensBeforeFunctionName(
-    const FunctionDecl &F, const ASTContext &Ctx, const SourceManager &SM,
-    const LangOptions &LangOpts) {
-  SourceLocation BeginF = expandIfMacroId(F.getBeginLoc(), SM);
-  SourceLocation BeginNameF = expandIfMacroId(F.getLocation(), SM);
-
-  // Create tokens for everything before the name of the function.
-  std::pair<FileID, unsigned> Loc = SM.getDecomposedLoc(BeginF);
-  StringRef File = SM.getBufferData(Loc.first);
-  const char *TokenBegin = File.data() + Loc.second;
-  Lexer Lexer(SM.getLocForStartOfFile(Loc.first), LangOpts, File.begin(),
-              TokenBegin, File.end());
-  Token T;
-  SmallVector<ClassifiedToken, 8> ClassifiedTokens;
-  while (!Lexer.LexFromRawLexer(T) &&
-         SM.isBeforeInTranslationUnit(T.getLocation(), BeginNameF)) {
-    if (T.is(tok::raw_identifier)) {
-      IdentifierInfo &Info = Ctx.Idents.get(
-          StringRef(SM.getCharacterData(T.getLocation()), T.getLength()));
-
-      if (Info.hasMacroDefinition()) {
-        const MacroInfo *MI = PP->getMacroInfo(&Info);
-        if (!MI || MI->isFunctionLike()) {
-          // Cannot handle function style macros.
-          diag(F.getLocation(), Message);
-          return std::nullopt;
-        }
-      }
-
-      T.setIdentifierInfo(&Info);
-      T.setKind(Info.getTokenID());
-    }
-
-    if (std::optional<ClassifiedToken> CT = classifyToken(F, *PP, T))
-      ClassifiedTokens.push_back(*CT);
-    else {
-      diag(F.getLocation(), Message);
-      return std::nullopt;
-    }
-  }
-
-  return ClassifiedTokens;
-}
-
 static bool hasAnyNestedLocalQualifiers(QualType Type) {
   bool Result = Type.hasLocalQualifiers();
   if (Type->isPointerType())
@@ -293,9 +203,11 @@
 
   // Include qualifiers to the left and right of the return type.
   std::optional<SmallVector<ClassifiedToken, 8>> MaybeTokens =
-      classifyTokensBeforeFunctionName(F, Ctx, SM, LangOpts);
-  if (!MaybeTokens)
+      utils::type::classifyDeclTypeTokens(*PP, F, Ctx);
+  if (!MaybeTokens) {
+    diag(F.getLocation(), Message);
     return {};
+  }
   const SmallVector<ClassifiedToken, 8> &Tokens = *MaybeTokens;
 
   ReturnTypeRange.setBegin(expandIfMacroId(ReturnTypeRange.getBegin(), SM));
@@ -345,9 +257,11 @@
   // Tokenize return type. If it contains macros which contain a mix of
   // qualifiers, specifiers and types, give up.
   std::optional<SmallVector<ClassifiedToken, 8>> MaybeTokens =
-      classifyTokensBeforeFunctionName(F, Ctx, SM, LangOpts);
-  if (!MaybeTokens)
+      utils::type::classifyDeclTypeTokens(*PP, F, Ctx);
+  if (!MaybeTokens) {
+    diag(F.getLocation(), Message);
     return;
+  }
 
   // Find specifiers, remove them from the return type, add them to 'auto'.
   unsigned int ReturnTypeBeginOffset =
Index: clang-tools-extra/clang-tidy/modernize/AvoidCArraysCheck.h
===================================================================
--- clang-tools-extra/clang-tidy/modernize/AvoidCArraysCheck.h
+++ clang-tools-extra/clang-tidy/modernize/AvoidCArraysCheck.h
@@ -10,6 +10,7 @@
 #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_AVOIDCARRAYSCHECK_H
 
 #include "../ClangTidyCheck.h"
+#include "../utils/IncludeInserter.h"
 
 namespace clang::tidy::modernize {
 
@@ -19,13 +20,27 @@
 /// http://clang.llvm.org/extra/clang-tidy/checks/modernize/avoid-c-arrays.html
 class AvoidCArraysCheck : public ClangTidyCheck {
 public:
-  AvoidCArraysCheck(StringRef Name, ClangTidyContext *Context)
-      : ClangTidyCheck(Name, Context) {}
+  AvoidCArraysCheck(StringRef Name, ClangTidyContext *Context);
+  void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
+                           Preprocessor *ModuleExpanderPP) override;
   bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
     return LangOpts.CPlusPlus11;
   }
   void registerMatchers(ast_matchers::MatchFinder *Finder) override;
   void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+  void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+
+private:
+  Preprocessor *PP = nullptr;
+  utils::IncludeInserter IncludeInserter;
+  bool replaceDecl(ArrayTypeLoc ATL, const VarDecl *Var, bool UseCTAD,
+                   ASTContext &Context, std::vector<FixItHint> &FixIts);
+  bool replaceArrayReferences(const VarDecl *Var, const FunctionDecl *Func,
+                              ASTContext &Context,
+                              std::vector<FixItHint> &FixIts);
+  std::vector<FixItHint> replaceArray(ArrayTypeLoc ATL,
+                                      const DeclStmt *VarDeclStmt,
+                                      const VarDecl *Var, ASTContext &Context);
 };
 
 } // namespace clang::tidy::modernize
Index: clang-tools-extra/clang-tidy/modernize/AvoidCArraysCheck.cpp
===================================================================
--- clang-tools-extra/clang-tidy/modernize/AvoidCArraysCheck.cpp
+++ clang-tools-extra/clang-tidy/modernize/AvoidCArraysCheck.cpp
@@ -7,8 +7,14 @@
 //===----------------------------------------------------------------------===//
 
 #include "AvoidCArraysCheck.h"
+#include "../utils/LexerUtils.h"
+#include "../utils/TypeUtils.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Lex/Lexer.h"
+#include "clang/Lex/Preprocessor.h"
+#include <optional>
+#include <tuple>
 
 using namespace clang::ast_matchers;
 
@@ -40,9 +46,14 @@
 
 namespace clang::tidy::modernize {
 
+using utils::type::ClassifiedToken;
+
 void AvoidCArraysCheck::registerMatchers(MatchFinder *Finder) {
   Finder->addMatcher(
       typeLoc(hasValidBeginLoc(), hasType(arrayType()),
+              optionally(hasParent(varDecl(hasParent(declStmt().bind("decl")),
+                                           hasAncestor(functionDecl()))
+                                       .bind("var"))),
               unless(anyOf(hasParent(parmVarDecl(isArgvOfMain())),
                            hasParent(varDecl(isExternC())),
                            hasParent(fieldDecl(
@@ -52,13 +63,515 @@
       this);
 }
 
+SourceLocation findEndOfToken(SourceLocation Loc, const ASTContext &Context) {
+  const SourceManager &SM = Context.getSourceManager();
+  const LangOptions &LangOpts = Context.getLangOpts();
+
+  SourceLocation Orig = Loc;
+  Loc = Loc.getLocWithOffset(1);
+  while (Loc.isValid()) {
+    SourceLocation PrevTokenLoc =
+        utils::lexer::findPreviousTokenStart(Loc, SM, LangOpts);
+    if (PrevTokenLoc != Orig)
+      return Loc.getLocWithOffset(-1);
+    Loc = Loc.getLocWithOffset(1);
+  }
+  return SourceLocation{};
+}
+
+SourceLocation getPreviousTokenLoc(SourceLocation Location,
+                                   const SourceManager &SM,
+                                   const LangOptions &LangOpts,
+                                   bool SkipComments = false) {
+  Token Token;
+  Token.setKind(tok::unknown);
+
+  Location = Location.getLocWithOffset(-1);
+  if (Location.isInvalid())
+    return Location;
+
+  SourceLocation StartOfFile = SM.getLocForStartOfFile(SM.getFileID(Location));
+  while (Location != StartOfFile) {
+    Location = Lexer::GetBeginningOfToken(Location, SM, LangOpts);
+    if (!Lexer::getRawToken(Location, Token, SM, LangOpts) &&
+        (!SkipComments || !Token.is(tok::comment)))
+      break;
+    Location = Location.getLocWithOffset(-1);
+  }
+  return Location;
+}
+
+SourceLocation getVarLocForReplacement(const VarDecl *Var,
+                                       const ASTContext &Context) {
+  const SourceManager &SM = Context.getSourceManager();
+  const LangOptions &LangOpts = Context.getLangOpts();
+  SourceLocation EndLoc = getPreviousTokenLoc(Var->getLocation(), SM, LangOpts);
+  if (!EndLoc.isValid())
+    return SourceLocation{};
+  EndLoc = findEndOfToken(EndLoc, Context);
+  if (!EndLoc.isValid())
+    return SourceLocation{};
+  CharSourceRange Range{SourceRange{EndLoc, EndLoc.getLocWithOffset(1)}, false};
+  StringRef Text = Lexer::getSourceText(Range, SM, LangOpts);
+  if (Text.empty())
+    return SourceLocation{};
+  if (std::isspace(Text[0]))
+    return EndLoc.getLocWithOffset(1);
+  return EndLoc;
+}
+
+// Collects the qualifiers, specifiers, and type tokens and combines them
+// into source text strings suitable for std::array<>/std::vector<>
+// replacement. Specifiers are placed outside of the array/vector template
+// type, and the qualifiers and type tokens themselves are placed within
+// the array/vector template type.
+//
+// Any text between the final non-specifer token and the following specifier
+// token is moved to appear after the ">" in the eventual
+// std::array<>/std::vector<> declaration.
+//
+// The logic works in two passes: first, classifying the tokens and collecting
+// the source text associated with each token including any whitespace/comments
+// that should remain "attached" to the token. Second, the tokens and text
+// are parsed using a "simple" algorithm to determine whether qualifiers are
+// associated with the array, or the array element. In particular, handling
+// pointers requires extra care to distinguish a qualifier on the pointer type
+// vs qualifier on the type being pointed to. Since QualType/TypeLoc do not
+// store per qualifier source location information, this bespoke logic cannot
+// handle every possible case (see tests marked with FIXMEs), but it gets
+// the most common cases.
+std::optional<std::tuple<std::string, std::string, std::string>>
+extractTypeComponents(Preprocessor *PP, TypeLoc TL, const VarDecl *Var,
+                      const ASTContext &Context) {
+  const SourceManager &SM = Context.getSourceManager();
+  const LangOptions &LangOpts = Context.getLangOpts();
+
+  SourceLocation EndLoc = getVarLocForReplacement(Var, Context);
+  if (!EndLoc.isValid())
+    return std::nullopt;
+  std::optional<SmallVector<ClassifiedToken, 8>> MaybeTokens =
+      utils::type::classifyDeclTypeTokens(*PP, Var->getBeginLoc(), EndLoc,
+                                          Context);
+  if (!MaybeTokens || MaybeTokens->size() == 0)
+    return std::nullopt;
+
+  unsigned LastTypeIndex = -1;
+  for (auto [Index, Token] : llvm::enumerate(*MaybeTokens)) {
+    if (!Token.IsSpecifier && !Token.IsQualifier)
+      LastTypeIndex = Index;
+  }
+
+  struct TokenAndText {
+    ClassifiedToken T;
+    std::string Text;
+
+    TokenAndText(ClassifiedToken T, StringRef Text) : T(T), Text(Text) {}
+  };
+
+  std::string Specifiers;
+  std::string QualType;
+  std::string BaseType;
+  std::string Suffix;
+
+  SmallVector<TokenAndText, 8> Tokens;
+  for (auto [Index, Current] : llvm::enumerate(*MaybeTokens)) {
+    if (Index == MaybeTokens->size() - 1) {
+      if (Index == LastTypeIndex) {
+        CharSourceRange CSR{SourceRange{Current.T.getLocation()}, true};
+        Tokens.emplace_back(Current, Lexer::getSourceText(CSR, SM, LangOpts));
+
+        SourceLocation SuffixStart =
+            findEndOfToken(Current.T.getLocation(), Context);
+        Suffix = Lexer::getSourceText(
+            CharSourceRange{SourceRange{SuffixStart, EndLoc}, false}, SM,
+            LangOpts);
+      } else {
+        CharSourceRange CSR{SourceRange{Current.T.getLocation(), EndLoc},
+                            false};
+        Tokens.emplace_back(Current, Lexer::getSourceText(CSR, SM, LangOpts));
+      }
+    } else {
+      const ClassifiedToken &Next = (*MaybeTokens)[Index + 1];
+      if (Index == LastTypeIndex) {
+        Tokens.emplace_back(
+            Current,
+            Lexer::getSourceText(
+                CharSourceRange{SourceRange{Current.T.getLocation()}, true}, SM,
+                LangOpts));
+
+        SourceLocation SuffixStart =
+            findEndOfToken(Current.T.getLocation(), Context);
+        Suffix = Lexer::getSourceText(
+            CharSourceRange{SourceRange{SuffixStart, Next.T.getLocation()},
+                            false},
+            SM, LangOpts);
+      } else {
+        Tokens.emplace_back(
+            Current, Lexer::getSourceText(
+                         CharSourceRange{SourceRange{Current.T.getLocation(),
+                                                     Next.T.getLocation()},
+                                         false},
+                         SM, LangOpts));
+      }
+    }
+  }
+
+  // Track whether we've seen a '*' token while scanning right to left.
+  bool ConsumedStar = false;
+  std::optional<SourceLocation> StarLoc;
+  if (PointerTypeLoc PTL = TL.getUnqualifiedLoc().getAs<PointerTypeLoc>())
+    StarLoc.emplace(PTL.getStarLoc());
+
+  // Track if we correctly found the CV qualifiers of the array. After
+  // processing the tokens, validate our assumptions and bail out if
+  // any assumption is not correct.
+  bool SawConst = false;
+  bool SawVolatile = false;
+
+  // TypeLoc'c SourceLocation starts at the first non-qualifier, so any
+  // associated type qualifier is not included. However, type qualifiers
+  // may be embeded in the type ('unsigned const int').
+  auto WithinTypeRange = [&, UnqualifiedTL = TL.getUnqualifiedLoc()](
+                             const ClassifiedToken &Token) -> bool {
+    if (!Token.IsQualifier)
+      return false;
+
+    FullSourceLoc TokLoc{Token.T.getLocation(), SM};
+    return FullSourceLoc(UnqualifiedTL.getBeginLoc(), SM)
+               .isBeforeInTranslationUnitThan(TokLoc) &&
+           TokLoc.isBeforeInTranslationUnitThan(UnqualifiedTL.getEndLoc());
+  };
+  auto IsSpec = [&](const ClassifiedToken &Token) -> bool {
+    if (WithinTypeRange(Token))
+      return false;
+
+    if (ConsumedStar)
+      return Token.IsSpecifier;
+    else
+      return Token.IsSpecifier || Token.IsQualifier;
+  };
+  auto AppendTypeToken = [&](const ClassifiedToken &Token, StringRef Text) {
+    if (StarLoc.has_value() && Token.T.getLocation() == *StarLoc)
+      ConsumedStar = true;
+
+    if (!WithinTypeRange(Token) && !ConsumedStar) {
+      if (Token.T.is(tok::kw_const))
+        SawConst = true;
+      if (Token.T.is(tok::kw_volatile))
+        SawVolatile = true;
+    }
+
+    if (IsSpec(Token)) {
+      Specifiers = (Text + Specifiers).str();
+    } else {
+      if (ConsumedStar)
+        QualType = (Text + QualType).str();
+      else
+        BaseType = (Text + BaseType).str();
+    }
+  };
+  for (const auto &T : llvm::reverse(Tokens))
+    AppendTypeToken(T.T, T.Text);
+
+  if (ConsumedStar)
+    Specifiers += BaseType;
+  else {
+    assert(QualType.size() == 0);
+    QualType = BaseType;
+  }
+
+  if (TL.getType().isLocalVolatileQualified() ^ SawVolatile)
+    return {};
+  if (TL.getType().isLocalConstQualified() ^ SawConst)
+    return {};
+
+  return std::make_tuple(std::move(Specifiers), std::move(QualType),
+                         std::move(Suffix));
+}
+
+bool AvoidCArraysCheck::replaceDecl(ArrayTypeLoc ATL, const VarDecl *Var,
+                                    bool UseCTAD, ASTContext &Context,
+                                    std::vector<FixItHint> &FixIts) {
+  const SourceManager &SM = Context.getSourceManager();
+  const LangOptions &LangOpts = Context.getLangOpts();
+
+  std::optional<std::tuple<std::string, std::string, std::string>>
+      TypeComponents =
+          extractTypeComponents(PP, ATL.getElementLoc(), Var, Context);
+  if (!TypeComponents)
+    return {};
+  auto [Specifiers, QualType, Suffix] = *TypeComponents;
+
+  if (UseCTAD && Suffix.empty())
+    Suffix = " ";
+
+  std::string Replacement;
+  const bool IsVLA = ATL.getTypePtr()->isVariableArrayType();
+  const bool IsCharElem =
+      ATL.getElementLoc().getTypePtr()->isAnyCharacterType();
+  if (UseCTAD) {
+    // FIXME - support vector
+    if (IsVLA)
+      return false;
+    else
+      Replacement += Specifiers + "std::array" + Suffix;
+  } else {
+    // FIXME - support vector
+    if (IsVLA)
+      return false;
+    else
+      Replacement += Specifiers + "std::array<" + QualType + ", ";
+    if (ATL.getSizeExpr()) {
+      Replacement += Lexer::getSourceText(
+          CharSourceRange::getCharRange(
+              ATL.getLBracketLoc().getLocWithOffset(1), ATL.getRBracketLoc()),
+          SM, LangOpts);
+    } else {
+      if (!Var->hasInit())
+        return false;
+
+      const Expr *InitExpr = Var->getInit();
+      if (const auto *EWC = dyn_cast<ExprWithCleanups>(InitExpr))
+        InitExpr = EWC->getSubExpr();
+
+      if (const auto *ILE = dyn_cast<InitListExpr>(InitExpr)) {
+        if (IsCharElem) {
+          if (ILE->getNumInits() != 1)
+            return false;
+          const Expr *FirstInitExpr = ILE->getInit(0);
+          if (const auto *SE = dyn_cast<StringLiteral>(FirstInitExpr))
+            Replacement += std::to_string(SE->getLength() + 1);
+          else
+            return false;
+        } else {
+          Replacement += std::to_string(ILE->getNumInits());
+        }
+      } else if (const auto *SE = dyn_cast<StringLiteral>(InitExpr))
+        Replacement += std::to_string(SE->getLength() + 1);
+      else
+        return false;
+    }
+    Replacement += ">" + Suffix;
+  }
+
+  SourceLocation EndLoc = getVarLocForReplacement(Var, Context);
+  CharSourceRange ReplacementRange =
+      CharSourceRange::getCharRange(Var->getBeginLoc(), EndLoc);
+
+  FixIts.push_back(FixItHint::CreateReplacement(ReplacementRange, Replacement));
+  FixIts.push_back(FixItHint::CreateRemoval(ATL.getBracketsRange()));
+  return true;
+}
+
+bool AvoidCArraysCheck::replaceArrayReferences(const VarDecl *Var,
+                                               const FunctionDecl *Func,
+                                               ASTContext &Context,
+                                               std::vector<FixItHint> &FixIts) {
+  const SourceManager &SM = Context.getSourceManager();
+  const LangOptions &LangOpts = Context.getLangOpts();
+
+  auto NonParensExpr = [](auto &&...Matchers) {
+    return expr(std::forward<decltype(Matchers)>(Matchers)...,
+                unless(parenExpr()));
+  };
+  SmallVector<BoundNodes, 4> Matches = match(
+      findAll(declRefExpr(
+                  to(equalsNode(Var)), unless(hasAncestor(typeAliasDecl())),
+                  optionally(hasParent(varDecl(hasParent(declStmt(
+                      hasParent(cxxForRangeStmt().bind("range-for"))))))),
+                  optionally(hasAncestor(
+                      NonParensExpr(
+                          optionally(unaryExprOrTypeTraitExpr().bind("sizeof")),
+                          optionally(
+                              hasAncestor(NonParensExpr().bind("parent2Expr"))))
+                          .bind("parent1"))))
+                  .bind("ref")),
+      *Func->getBody(), Context);
+  for (const BoundNodes &Match : Matches) {
+    const DeclRefExpr *Ref = Match.getNodeAs<DeclRefExpr>("ref");
+    if (Match.getNodeAs<CXXForRangeStmt>("range-for"))
+      continue;
+    if (Match.getNodeAs<UnaryExprOrTypeTraitExpr>("sizeof"))
+      continue;
+
+    const Expr *Parent1 = Match.getNodeAs<Expr>("parent1");
+    const Expr *Parent2Expr = Match.getNodeAs<Expr>("parent2Expr");
+    if (!Parent1)
+      return false;
+
+    if (const auto *Cast = dyn_cast<ImplicitCastExpr>(Parent1)) {
+      if (Cast->getCastKind() != CK_ArrayToPointerDecay)
+        return false;
+      if (const auto *Subscript =
+              dyn_cast_or_null<ArraySubscriptExpr>(Parent2Expr)) {
+        if (Subscript->getLHS() == Parent1) {
+          // Nothing to change in this case, e.g., 'arr[1]'
+          continue;
+        } else {
+          // Swap order of subscript, e.g., '1[arr]'
+          const bool NotTokenRange = true;
+          CharSourceRange NameRange{
+              SourceRange{Subscript->getBase()->getBeginLoc(),
+                          Subscript->getBase()->getEndLoc()},
+              NotTokenRange};
+          StringRef Name = Lexer::getSourceText(NameRange, SM, LangOpts);
+          CharSourceRange IdxRange{
+              SourceRange{Subscript->getIdx()->getBeginLoc(),
+                          Subscript->getIdx()->getEndLoc()},
+              NotTokenRange};
+          StringRef Idx = Lexer::getSourceText(IdxRange, SM, LangOpts);
+          FixIts.push_back(FixItHint::CreateReplacement(NameRange, Idx));
+          FixIts.push_back(FixItHint::CreateReplacement(IdxRange, Name));
+        }
+      } else {
+        const bool IsTokenRange = true;
+        if (Ref->getBeginLoc().isMacroID())
+          return false;
+
+        CharSourceRange NameRange{
+            SourceRange{Ref->getBeginLoc(), Ref->getEndLoc()}, IsTokenRange};
+        StringRef Name = Lexer::getSourceText(NameRange, SM, LangOpts);
+        FixIts.push_back(
+            FixItHint::CreateReplacement(NameRange, (Name + ".begin()").str()));
+        continue;
+      }
+    } else
+      return false;
+  }
+
+  return true;
+}
+
+std::vector<FixItHint>
+AvoidCArraysCheck::replaceArray(ArrayTypeLoc ATL, const DeclStmt *VarDeclStmt,
+                                const VarDecl *Var, ASTContext &Context) {
+  const FunctionDecl *Func = dyn_cast<FunctionDecl>(Var->getDeclContext());
+  if (!Func || !Func->getBody())
+    return {};
+
+  const SourceManager &SM = Context.getSourceManager();
+  const LangOptions &LangOpts = Context.getLangOpts();
+
+  const Type *ElemType = ATL.getElementLoc().getTypePtr();
+  if (ElemType->isArrayType() || ElemType->isFunctionPointerType() ||
+      ElemType->isMemberFunctionPointerType())
+    return {};
+
+  std::vector<FixItHint> FixIts;
+  if (!replaceArrayReferences(Var, Func, Context, FixIts))
+    return {};
+
+  if (!VarDeclStmt->isSingleDecl())
+    return {};
+
+  const Expr *InitExpr = Var->hasInit() ? Var->getInit() : nullptr;
+  if (const auto *EWC = dyn_cast_or_null<ExprWithCleanups>(InitExpr))
+    InitExpr = EWC->getSubExpr();
+
+  // Determine if we can use a CTAD declaration
+  const bool IsCharElem =
+      ATL.getElementLoc().getTypePtr()->isAnyCharacterType();
+  bool UseCTAD = false;
+  if (getLangOpts().CPlusPlus17 && InitExpr && !ATL.getSizeExpr() &&
+      !IsCharElem) {
+    if (const auto *ILE = dyn_cast<InitListExpr>(Var->getInit())) {
+      if (ILE->getNumInits() > 0) {
+        UseCTAD = llvm::all_of(
+            ILE->inits(),
+            [ElemType =
+                 ATL.getElementLoc().getType().getTypePtr()](const Expr *E) {
+              const Expr *SpelledExpr = E->IgnoreUnlessSpelledInSource();
+              if (dyn_cast<InitListExpr>(SpelledExpr))
+                return false;
+              const auto *ConstructExpr =
+                  dyn_cast<CXXConstructExpr>(SpelledExpr);
+              if (ConstructExpr &&
+                  !dyn_cast<CXXTemporaryObjectExpr>(SpelledExpr) &&
+                  ConstructExpr->isListInitialization())
+                return false;
+              return SpelledExpr->getType().getTypePtr() == ElemType;
+            });
+      }
+    }
+  }
+
+  // Add FixIt {} around initializer
+  if (InitExpr && !UseCTAD) {
+    SourceRange InitRange;
+    if (const auto *SE = dyn_cast<StringLiteral>(InitExpr)) {
+      std::optional<Token> NextToken =
+          Lexer::findNextToken(SE->getEndLoc(), SM, LangOpts);
+      if (!NextToken)
+        return {};
+      InitRange = SourceRange{SE->getBeginLoc(), NextToken->getLocation()};
+    } else if (const auto *ILE = dyn_cast<InitListExpr>(InitExpr)) {
+      std::optional<Token> NextToken =
+          Lexer::findNextToken(ILE->getEndLoc(), SM, LangOpts);
+      if (!NextToken)
+        return {};
+      InitRange = SourceRange{ILE->getBeginLoc(), NextToken->getLocation()};
+    }
+
+    if (InitRange.isValid()) {
+      if (utils::lexer::rangeContainsExpansionsOrDirectives(InitRange, SM,
+                                                            LangOpts))
+        return {};
+      FixIts.push_back(FixItHint::CreateInsertion(InitRange.getBegin(), "{"));
+      FixIts.push_back(FixItHint::CreateInsertion(InitRange.getEnd(), "}"));
+    }
+  }
+
+  if (!replaceDecl(ATL, Var, UseCTAD, Context, FixIts))
+    return {};
+
+  std::optional<FixItHint> IncludeFixIt =
+      IncludeInserter.createIncludeInsertion(SM.getFileID(Var->getBeginLoc()),
+                                             "<array>");
+  if (IncludeFixIt)
+    FixIts.push_back(std::move(*IncludeFixIt));
+
+  return FixIts;
+}
+
 void AvoidCArraysCheck::check(const MatchFinder::MatchResult &Result) {
   const auto *ArrayType = Result.Nodes.getNodeAs<TypeLoc>("typeloc");
 
-  diag(ArrayType->getBeginLoc(),
-       "do not declare %select{C-style|C VLA}0 arrays, use "
-       "%select{std::array<>|std::vector<>}0 instead")
-      << ArrayType->getTypePtr()->isVariableArrayType();
+  bool IsVLA = ArrayType->getTypePtr()->isVariableArrayType();
+  auto Diag = diag(ArrayType->getBeginLoc(),
+                   "do not declare %select{C-style|C VLA}0 arrays, use "
+                   "%select{std::array<>|std::vector<>}0 instead")
+              << IsVLA;
+
+  const auto *Var = Result.Nodes.getNodeAs<VarDecl>("var");
+  const auto *VarDeclStmt = Result.Nodes.getNodeAs<DeclStmt>("decl");
+  if (!Var || !VarDeclStmt)
+    return;
+
+  // FIXME: Support variables with attributes
+  if (Var->hasAttrs())
+    return;
+
+  ArrayTypeLoc ATL = ArrayType->getUnqualifiedLoc().getAs<ArrayTypeLoc>();
+  if (!ATL)
+    return;
+  Diag << replaceArray(ATL, VarDeclStmt, Var, *Result.Context);
+}
+
+void AvoidCArraysCheck::registerPPCallbacks(const SourceManager &SM,
+                                            Preprocessor *PP,
+                                            Preprocessor *ModuleExpanderPP) {
+  this->PP = PP;
+  IncludeInserter.registerPreprocessor(PP);
+}
+
+void AvoidCArraysCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+  Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle());
 }
 
+AvoidCArraysCheck::AvoidCArraysCheck(StringRef Name, ClangTidyContext *Context)
+    : ClangTidyCheck(Name, Context),
+      IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",
+                                               utils::IncludeSorter::IS_LLVM),
+                      areDiagsSelfContained()) {}
+
 } // namespace clang::tidy::modernize
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to