This is an archive of the discontinued LLVM Phabricator instance.

[clang-tidy] Add new check cppcoreguidelines-pro-bounds-array-to-pointer-decay
ClosedPublic

Authored by mgehre on Oct 11 2015, 4:24 PM.

Details

Summary

This check flags all array to pointer decays.

Pointers should not be used as arrays. array_view is a bounds-checked,
safe alternative to using pointers to access arrays.

This rule is part of the "Bounds safety" profile of the C++ Core
Guidelines, see
https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#-bounds3-no-array-to-pointer-decay

Diff Detail

Repository
rL LLVM

Event Timeline

mgehre updated this revision to Diff 37069.Oct 11 2015, 4:24 PM
mgehre retitled this revision from to [clang-tidy] Add new check cppcoreguidelines-pro-bounds-array-to-pointer-decay.
mgehre updated this object.
mgehre added a subscriber: cfe-commits.
sbenza added inline comments.Oct 12 2015, 2:33 PM
clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.cpp
29 ↗(On Diff #37069)

__range is an implementation detail and should not be used here.
Check that this is the range init of a cxxForRangeStmt parent node or something like that.

test/clang-tidy/cppcoreguidelines-pro-bounds-array-to-pointer-decay.cpp
12 ↗(On Diff #37069)

this is not implicit. Is the 'implicit' optional?

mgehre added inline comments.Oct 12 2015, 3:57 PM
clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.cpp
29 ↗(On Diff #37069)

That was my first attempt, but I couldn't quite figure it out.

I tried

unless(hasAncestor(cxxForRangeStmt(hasRangeInit(hasDescendant(equalsBoundNode("cast"))))))

but that does not compile.

Any ideas?

test/clang-tidy/cppcoreguidelines-pro-bounds-array-to-pointer-decay.cpp
12 ↗(On Diff #37069)

Currently, the diag is

diag(MatchedCast->getExprLoc(), "do not (implicitly) convert an array to a pointer");

should I make the parenthesis conditional?

klimek added a subscriber: klimek.Oct 13 2015, 12:44 AM
klimek added inline comments.
clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.cpp
40 ↗(On Diff #37069)

Can't we provide a fixit?

aaron.ballman added inline comments.Oct 13 2015, 7:19 AM
clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.cpp
29 ↗(On Diff #37069)

I'm not certain that you'll be able to do that. Looking at the AST dump:

TranslationUnitDecl 0xa40168 <<invalid sloc>> <invalid sloc>
|-TypedefDecl 0xa40458 <<invalid sloc>> <invalid sloc> implicit __builtin_va_list 'char *'
`-FunctionDecl 0xa404c0 <E:\Desktop\test1.cpp:1:1, line:5:1> line:1:6 f 'void (void)'
  `-CompoundStmt 0xa40b88 <col:10, line:5:1>
    |-DeclStmt 0xa40600 <line:2:3, col:11>
    | `-VarDecl 0xa405c8 <col:3, col:10> col:7 used a 'int [5]'
    `-CXXForRangeStmt 0xa40b50 <line:3:3, line:4:5>
      |-DeclStmt 0xa40828 <line:3:16>
      | `-VarDecl 0xa406b8 <col:16> col:16 implicit used __range 'int (&&)[5]' cinit
      |   `-DeclRefExpr 0xa40610 <col:16> 'int [5]' lvalue Var 0xa405c8 'a' 'int [5]'
      |-DeclStmt 0xa40a00 <col:14>
      | |-VarDecl 0xa40870 <col:14> col:14 implicit used __begin 'int *':'int *' cinit
      | | `-ImplicitCastExpr 0xa40960 <col:14> 'int *' <ArrayToPointerDecay>
      | |   `-DeclRefExpr 0xa40838 <col:14> 'int [5]' lvalue Var 0xa406b8 '__range' 'int (&&)[5]'
      | `-VarDecl 0xa408b0 <col:14, col:16> col:14 implicit used __end 'int *':'int *' cinit
      |   `-BinaryOperator 0xa409a0 <col:14, col:16> 'int *' '+'
      |     |-ImplicitCastExpr 0xa40990 <col:14> 'int *' <ArrayToPointerDecay>
      |     | `-DeclRefExpr 0xa40850 <col:14> 'int [5]' lvalue Var 0xa406b8 '__range' 'int (&&)[5]'
      |     `-IntegerLiteral 0xa40970 <col:16> 'int' 5
      |-BinaryOperator 0xa40a60 <col:14> '_Bool' '!='
      | |-ImplicitCastExpr 0xa40a40 <col:14> 'int *':'int *' <LValueToRValue>
      | | `-DeclRefExpr 0xa40a10 <col:14> 'int *':'int *' lvalue Var 0xa40870 '__begin' 'int *':'int *'
      | `-ImplicitCastExpr 0xa40a50 <col:14> 'int *':'int *' <LValueToRValue>
      |   `-DeclRefExpr 0xa40a28 <col:14> 'int *':'int *' lvalue Var 0xa408b0 '__end' 'int *':'int *'
      |-UnaryOperator 0xa40a90 <col:14> 'int *':'int *' lvalue prefix '++'
      | `-DeclRefExpr 0xa40a78 <col:14> 'int *':'int *' lvalue Var 0xa40870 '__begin' 'int *':'int *'
      |-DeclStmt 0xa40680 <col:7, col:18>
      | `-VarDecl 0xa40648 <col:7, col:14> col:12 e 'int':'int' cinit
      |   `-ImplicitCastExpr 0xa40b40 <col:14> 'int' <LValueToRValue>
      |     `-UnaryOperator 0xa40ad0 <col:14> 'int' lvalue prefix '*'
      |       `-ImplicitCastExpr 0xa40ac0 <col:14> 'int *':'int *' <LValueToRValue>
      |         `-DeclRefExpr 0xa40aa8 <col:14> 'int *':'int *' lvalue Var 0xa40870 '__begin' 'int *':'int *'
      `-NullStmt 0xa40b78 <line:4:5>

There is no range init in the AST. I'm not certain if that is an issue we want to rectify in the AST or not.

40 ↗(On Diff #37069)

I think the fixit that the C++ Core Guidelines wants is to use array_view, which isn't available to everyone and can't be applied locally (for instance, it may require changing a function parameter instead of the argument passed to the function).

test/clang-tidy/cppcoreguidelines-pro-bounds-array-to-pointer-decay.cpp
26 ↗(On Diff #37069)

I would like to see a test case demonstrating that this does not trigger a diagnostic when creating an array_view object. We don't need to pull in all of the array_view machinery from GSL, but it would be good to have a skeleton of the implementation to ensure we aren't diagnosing there.

klimek added inline comments.Oct 13 2015, 7:32 AM
clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.cpp
40 ↗(On Diff #37069)
aaron.ballman added inline comments.Oct 13 2015, 7:37 AM
clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.cpp
40 ↗(On Diff #37069)

Hmm, I missed that part, but you're right. I wonder if they intended that to be used as a workaround or not, however. It's not an array decay because it's an explicit subscript of the array, and a unary op. It's certainly a functional workaround, though. I would worry about more complex cases:

size_t g();
void h(int *);

void f() {
  int a[5];
  h(a + g() - 10); // Convert to &a[g() - 10];
}
mgehre updated this revision to Diff 37396.Oct 14 2015, 3:07 PM
mgehre marked an inline comment as done.

Test more complex array arithmetic, test gsl::array_view works, add "use array_view instead" to diagnostic

mgehre updated this revision to Diff 37417.Oct 14 2015, 4:00 PM
mgehre marked 7 inline comments as done.

Matcher does not rely on __range anymore

Updated commit to fix comments

aaron.ballman added inline comments.Oct 20 2015, 5:52 PM
test/clang-tidy/cppcoreguidelines-pro-bounds-array-to-pointer-decay.cpp
13 ↗(On Diff #37417)

Currently, the diag is

diag(MatchedCast->getExprLoc(), "do not (implicitly) convert an array to a pointer");

should I make the parenthesis conditional?

The core guideline only says to diagnose for implicit decay, so I think we should not diagnose in this case.

Perhaps the wording could be, "do not implicitly decay an array into a pointer; consider using gsl::array_view or an explicit decay instead"?

18 ↗(On Diff #37417)

Formatting

41 ↗(On Diff #37417)

Formatting (may want to just run clang-format over the file).

mgehre marked an inline comment as done.Oct 21 2015, 1:28 PM
mgehre added inline comments.
test/clang-tidy/cppcoreguidelines-pro-bounds-array-to-pointer-decay.cpp
13 ↗(On Diff #37417)

I posted the question here https://github.com/isocpp/CppCoreGuidelines/issues/352
From my understanding, explicit casts should also be forbidden for the same reasons that implicit decay was:
"Pointers are not arrays". If you need a pointer to the first element of an array, one can still use &a[0].

mgehre updated this revision to Diff 38353.Oct 25 2015, 11:34 AM
mgehre marked an inline comment as done.

Do not flag explicit casts

sbenza accepted this revision.Oct 26 2015, 11:08 AM
sbenza edited edge metadata.

Just one formatting comment.

clang-tidy/cppcoreguidelines/ProBoundsArrayToPointerDecayCheck.cpp
52 ↗(On Diff #38353)

reflow to 80 cols

This revision is now accepted and ready to land.Oct 26 2015, 11:08 AM
This revision was automatically updated to reflect the committed changes.