-
Notifications
You must be signed in to change notification settings - Fork 15.2k
Enforce SL.con.3: Add check to replace operator[] with at() #90043
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| //===--- AvoidBoundsErrorsCheck.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 "AvoidBoundsErrorsCheck.h" | ||
| #include "clang/ASTMatchers/ASTMatchFinder.h" | ||
| #include "clang/Lex/Lexer.h" | ||
|
|
||
| #include <iostream> | ||
| using namespace clang::ast_matchers; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please separate with newline. |
||
|
|
||
| namespace clang::tidy::cppcoreguidelines { | ||
|
|
||
| bool isApplicable(const QualType &Type) { | ||
|
||
| const auto TypeStr = Type.getAsString(); | ||
| bool Result = false; | ||
| // Only check for containers in the std namespace | ||
| if (TypeStr.find("std::vector") != std::string::npos) { | ||
| Result = true; | ||
| } | ||
| if (TypeStr.find("std::array") != std::string::npos) { | ||
| Result = true; | ||
| } | ||
| if (TypeStr.find("std::deque") != std::string::npos) { | ||
| Result = true; | ||
| } | ||
| if (TypeStr.find("std::map") != std::string::npos) { | ||
| Result = true; | ||
|
||
| } | ||
| if (TypeStr.find("std::unordered_map") != std::string::npos) { | ||
| Result = true; | ||
| } | ||
| if (TypeStr.find("std::flat_map") != std::string::npos) { | ||
| Result = true; | ||
| } | ||
|
||
| // TODO Add std::span with C++26 | ||
| return Result; | ||
| } | ||
|
|
||
| void AvoidBoundsErrorsCheck::registerMatchers(MatchFinder *Finder) { | ||
| Finder->addMatcher( | ||
| callExpr(callee(cxxMethodDecl(hasName("operator[]")).bind("f"))) | ||
|
||
| .bind("x"), | ||
| this); | ||
| } | ||
|
|
||
| void AvoidBoundsErrorsCheck::check(const MatchFinder::MatchResult &Result) { | ||
| const ASTContext &Context = *Result.Context; | ||
| const SourceManager &Source = Context.getSourceManager(); | ||
| const auto *MatchedExpr = Result.Nodes.getNodeAs<CallExpr>("x"); | ||
| const auto *MatchedFunction = Result.Nodes.getNodeAs<CXXMethodDecl>("f"); | ||
|
||
| const auto Type = MatchedFunction->getThisType(); | ||
|
||
| if (!isApplicable(Type)) { | ||
| return; | ||
| } | ||
|
|
||
| // Get original code. | ||
| const SourceLocation b(MatchedExpr->getBeginLoc()); | ||
| const SourceLocation e(MatchedExpr->getEndLoc()); | ||
| const std::string OriginalCode = | ||
| Lexer::getSourceText(CharSourceRange::getTokenRange(b, e), Source, | ||
| getLangOpts()) | ||
| .str(); | ||
| const auto Range = SourceRange(b, e); | ||
|
||
|
|
||
| // Build replacement. | ||
| std::string NewCode = OriginalCode; | ||
| const auto BeginOpen = NewCode.find("["); | ||
| NewCode.replace(BeginOpen, 1, ".at("); | ||
| const auto BeginClose = NewCode.find("]"); | ||
| NewCode.replace(BeginClose, 1, ")"); | ||
|
||
|
|
||
| diag(MatchedExpr->getBeginLoc(), "Do not use operator[], use at() instead.") | ||
|
||
| << FixItHint::CreateReplacement(Range, NewCode); | ||
|
||
| } | ||
|
|
||
| } // namespace clang::tidy::cppcoreguidelines | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| //===--- AvoidBoundsErrorsCheck.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_CPPCOREGUIDELINES_AVOIDBOUNDSERRORSCHECK_H | ||
| #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_AVOIDBOUNDSERRORSCHECK_H | ||
|
|
||
| #include "../ClangTidyCheck.h" | ||
|
|
||
| namespace clang::tidy::cppcoreguidelines { | ||
|
|
||
| /// Enforce CPP core guidelines SL.con.3 | ||
| /// | ||
| /// See | ||
| /// https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#slcon3-avoid-bounds-errors | ||
| /// For the user-facing documentation see: | ||
| /// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines/avoid-bounds-errors.html | ||
| class AvoidBoundsErrorsCheck : public ClangTidyCheck { | ||
| public: | ||
| AvoidBoundsErrorsCheck(StringRef Name, ClangTidyContext *Context) | ||
| : ClangTidyCheck(Name, Context) {} | ||
| void registerMatchers(ast_matchers::MatchFinder *Finder) override; | ||
| void check(const ast_matchers::MatchFinder::MatchResult &Result) override; | ||
| }; | ||
PiotrZSL marked this conversation as resolved.
Show resolved
Hide resolved
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ignore implicit code with TK_IgnoreUnlessSpelledInSource. |
||
|
|
||
| } // namespace clang::tidy::cppcoreguidelines | ||
|
|
||
| #endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_AVOIDBOUNDSERRORSCHECK_H | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,7 @@ | |
| #include "../performance/NoexceptMoveConstructorCheck.h" | ||
| #include "../performance/NoexceptSwapCheck.h" | ||
| #include "../readability/MagicNumbersCheck.h" | ||
| #include "AvoidBoundsErrorsCheck.h" | ||
| #include "AvoidCapturingLambdaCoroutinesCheck.h" | ||
| #include "AvoidConstOrRefDataMembersCheck.h" | ||
| #include "AvoidDoWhileCheck.h" | ||
|
|
@@ -57,6 +58,8 @@ namespace cppcoreguidelines { | |
| class CppCoreGuidelinesModule : public ClangTidyModule { | ||
| public: | ||
| void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { | ||
| CheckFactories.registerCheck<AvoidBoundsErrorsCheck>( | ||
| "cppcoreguidelines-avoid-bounds-errors"); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. check name cppcoreguidelines-avoid-bounds-errors is too generic for what check is doing, name it something like cppcoreguidelines-prefer-at-over-subscript-operator, or something simillar. Figure something out. |
||
| CheckFactories.registerCheck<AvoidCapturingLambdaCoroutinesCheck>( | ||
| "cppcoreguidelines-avoid-capturing-lambda-coroutines"); | ||
| CheckFactories.registerCheck<modernize::AvoidCArraysCheck>( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -131,6 +131,11 @@ New checks | |
| to reading out-of-bounds data due to inadequate or incorrect string null | ||
| termination. | ||
|
|
||
| - New :doc:`cppcoreguidelines-avoid-bounds-errors | ||
| <clang-tidy/checks/cppcoreguidelines/avoid-bounds-errors>` check. | ||
|
|
||
| Flags the unsafe `operator[]` and replaces it with `at()`. | ||
|
||
|
|
||
| - New :doc:`modernize-use-designated-initializers | ||
| <clang-tidy/checks/modernize/use-designated-initializers>` check. | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| .. title:: clang-tidy - cppcoreguidelines-avoid-bounds-errors | ||
|
|
||
| cppcoreguidelines-avoid-bounds-errors | ||
| ===================================== | ||
|
|
||
| This check enforces the `SL.con.3 <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#slcon3-avoid-bounds-errors>` guideline. | ||
|
||
| It flags all uses of `operator[]` on `std::vector`, `std::array`, `std::deque`, `std::map`, `std::unordered_map`, and `std::flat_map` and suggests to replace it with `at()`. | ||
| Note that `std::span` and `std::mdspan` do not support `at()` as of C++23, so the use of `operator[]` is not flagged. | ||
|
|
||
| For example the code | ||
|
|
||
| .. code-block:: c++ | ||
| std::array<int, 3> a; | ||
| int b = a[4]; | ||
|
|
||
| will be replaced by | ||
|
|
||
| .. code-block:: c++ | ||
| std::vector<int, 3> a; | ||
| int b = a.at(4); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. consider adding option to specify excluded classes, if class detection will be automatic. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| namespace std { | ||
| template<typename T, unsigned size> | ||
| struct array { | ||
| T operator[](unsigned i) { | ||
| return T{1}; | ||
| } | ||
| T at(unsigned i) { | ||
| return T{1}; | ||
| } | ||
| }; | ||
|
|
||
| template<typename T> | ||
| struct unique_ptr { | ||
| T operator[](unsigned i) { | ||
| return T{1}; | ||
| } | ||
| }; | ||
|
|
||
| template<typename T> | ||
| struct span { | ||
| T operator[](unsigned i) { | ||
| return T{1}; | ||
| } | ||
| }; | ||
| } // namespace std | ||
|
|
||
| namespace json { | ||
| template<typename T> | ||
| struct node{ | ||
| T operator[](unsigned i) { | ||
| return T{1}; | ||
| } | ||
| }; | ||
| } // namespace json | ||
|
|
||
|
|
||
| // RUN: %check_clang_tidy %s cppcoreguidelines-avoid-bounds-errors %t | ||
| std::array<int, 3> a; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add tests with:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As suggested, we added a test where the object is a template parameter and the method is called once with a class that has
|
||
|
|
||
| auto b = a[0]; | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: Do not use operator[], use at() instead. [cppcoreguidelines-avoid-bounds-errors] | ||
| // CHECK-FIXES: auto b = a.at(0); | ||
| auto c = a[1+1]; | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: Do not use operator[], use at() instead. [cppcoreguidelines-avoid-bounds-errors] | ||
| // CHECK-FIXES: auto c = a.at(1+1); | ||
| constexpr int index = 1; | ||
| auto d = a[index]; | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: Do not use operator[], use at() instead. [cppcoreguidelines-avoid-bounds-errors] | ||
| // CHECK-FIXES: auto d = a.at(index); | ||
|
|
||
| int e(int index) { | ||
| return a[index]; | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: Do not use operator[], use at() instead. [cppcoreguidelines-avoid-bounds-errors] | ||
| // CHECK-FIXES: return a.at(index); | ||
| } | ||
|
|
||
| auto f = a.at(0); | ||
|
|
||
| std::unique_ptr<int> p; | ||
| auto q = p[0]; | ||
|
|
||
| std::span<int> s; | ||
| auto t = s[0]; | ||
|
|
||
| json::node<int> n; | ||
| auto m = n[0]; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excessive newline.