diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt b/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt index 2fb4d7f1d7349..0abb000991859 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt @@ -21,6 +21,7 @@ add_clang_library(clangTidyCppCoreGuidelinesModule STATIC OwningMemoryCheck.cpp PreferMemberInitializerCheck.cpp ProBoundsArrayToPointerDecayCheck.cpp + ProBoundsAvoidUncheckedContainerAccess.cpp ProBoundsConstantArrayIndexCheck.cpp ProBoundsPointerArithmeticCheck.cpp ProTypeConstCastCheck.cpp diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp index 4b3b7bf963fdc..cc1ae156eef3e 100644 --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp @@ -36,6 +36,7 @@ #include "OwningMemoryCheck.h" #include "PreferMemberInitializerCheck.h" #include "ProBoundsArrayToPointerDecayCheck.h" +#include "ProBoundsAvoidUncheckedContainerAccess.h" #include "ProBoundsConstantArrayIndexCheck.h" #include "ProBoundsPointerArithmeticCheck.h" #include "ProTypeConstCastCheck.h" @@ -107,6 +108,8 @@ class CppCoreGuidelinesModule : public ClangTidyModule { "cppcoreguidelines-prefer-member-initializer"); CheckFactories.registerCheck( "cppcoreguidelines-pro-bounds-array-to-pointer-decay"); + CheckFactories.registerCheck( + "cppcoreguidelines-pro-bounds-avoid-unchecked-container-access"); CheckFactories.registerCheck( "cppcoreguidelines-pro-bounds-constant-array-index"); CheckFactories.registerCheck( diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsAvoidUncheckedContainerAccess.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsAvoidUncheckedContainerAccess.cpp new file mode 100644 index 0000000000000..35f432efa88ca --- /dev/null +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsAvoidUncheckedContainerAccess.cpp @@ -0,0 +1,262 @@ +//===--- ProBoundsAvoidUncheckedContainerAccess.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 "ProBoundsAvoidUncheckedContainerAccess.h" +#include "../utils/Matchers.h" +#include "../utils/OptionsUtils.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "llvm/ADT/StringRef.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::cppcoreguidelines { + +static constexpr llvm::StringRef DefaultExclusionStr = + "::std::map;::std::unordered_map;::std::flat_map"; + +ProBoundsAvoidUncheckedContainerAccess::ProBoundsAvoidUncheckedContainerAccess( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + ExcludedClasses(utils::options::parseStringList( + Options.get("ExcludeClasses", DefaultExclusionStr))), + FixMode(Options.get("FixMode", None)), + FixFunction(Options.get("FixFunction", "gsl::at")), + FixFunctionEmptyArgs(Options.get("FixFunctionEmptyArgs", FixFunction)) {} + +void ProBoundsAvoidUncheckedContainerAccess::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "ExcludeClasses", + utils::options::serializeStringList(ExcludedClasses)); + Options.store(Opts, "FixMode", FixMode); + Options.store(Opts, "FixFunction", FixFunction); + Options.store(Opts, "FixFunctionEmptyArgs", FixFunctionEmptyArgs); +} + +// TODO: if at() is defined in another class in the class hierarchy of the class +// that defines the operator[] we matched on, findAlternative() will not detect +// it. +static const CXXMethodDecl * +findAlternativeAt(const CXXMethodDecl *MatchedOperator) { + const CXXRecordDecl *Parent = MatchedOperator->getParent(); + const QualType SubscriptThisObjType = + MatchedOperator->getFunctionObjectParameterReferenceType(); + + for (const CXXMethodDecl *Method : Parent->methods()) { + // Require 'Method' to be as accessible as 'MatchedOperator' or more + if (MatchedOperator->getAccess() < Method->getAccess()) + continue; + + if (MatchedOperator->isConst() != Method->isConst()) + continue; + + const QualType AtThisObjType = + Method->getFunctionObjectParameterReferenceType(); + if (SubscriptThisObjType != AtThisObjType) + continue; + + if (!Method->getNameInfo().getName().isIdentifier() || + Method->getName() != "at") + continue; + + const bool SameReturnType = + Method->getReturnType() == MatchedOperator->getReturnType(); + if (!SameReturnType) + continue; + + const bool SameNumberOfArguments = + Method->getNumParams() == MatchedOperator->getNumParams(); + if (!SameNumberOfArguments) + continue; + + for (unsigned ArgInd = 0; ArgInd < Method->getNumParams(); ArgInd++) { + const bool SameArgType = + Method->parameters()[ArgInd]->getOriginalType() == + MatchedOperator->parameters()[ArgInd]->getOriginalType(); + if (!SameArgType) + continue; + } + + return Method; + } + return nullptr; +} + +void ProBoundsAvoidUncheckedContainerAccess::registerMatchers( + MatchFinder *Finder) { + Finder->addMatcher( + mapAnyOf(cxxOperatorCallExpr, cxxMemberCallExpr) + .with(callee( + cxxMethodDecl( + hasOverloadedOperatorName("[]"), + anyOf(parameterCountIs(0), parameterCountIs(1)), + unless(matchers::matchesAnyListedName(ExcludedClasses))) + .bind("operator"))) + .bind("caller"), + this); +} + +void ProBoundsAvoidUncheckedContainerAccess::check( + const MatchFinder::MatchResult &Result) { + + const auto *MatchedExpr = Result.Nodes.getNodeAs("caller"); + + if (FixMode == None) { + diag(MatchedExpr->getCallee()->getBeginLoc(), + "possibly unsafe 'operator[]', consider bounds-safe alternatives") + << MatchedExpr->getCallee()->getSourceRange(); + return; + } + + if (const auto *OCE = dyn_cast(MatchedExpr)) { + // Case: a[i] + const auto LeftBracket = SourceRange(OCE->getCallee()->getBeginLoc(), + OCE->getCallee()->getBeginLoc()); + const auto RightBracket = + SourceRange(OCE->getOperatorLoc(), OCE->getOperatorLoc()); + + if (FixMode == At) { + // Case: a[i] => a.at(i) + const auto *MatchedOperator = + Result.Nodes.getNodeAs("operator"); + const CXXMethodDecl *Alternative = findAlternativeAt(MatchedOperator); + + if (!Alternative) { + diag(MatchedExpr->getCallee()->getBeginLoc(), + "possibly unsafe 'operator[]', consider " + "bounds-safe alternatives") + << MatchedExpr->getCallee()->getSourceRange(); + return; + } + + diag(MatchedExpr->getCallee()->getBeginLoc(), + "possibly unsafe 'operator[]', consider " + "bounds-safe alternative 'at()'") + << MatchedExpr->getCallee()->getSourceRange() + << FixItHint::CreateReplacement(LeftBracket, ".at(") + << FixItHint::CreateReplacement(RightBracket, ")"); + + diag(Alternative->getBeginLoc(), "viable 'at()' is defined here", + DiagnosticIDs::Note) + << Alternative->getNameInfo().getSourceRange(); + + } else if (FixMode == Function) { + // Case: a[i] => f(a, i) + // + // Since C++23, the subscript operator may also be called without an + // argument, which makes the following distinction necessary + const bool EmptySubscript = + MatchedExpr->getDirectCallee()->getNumParams() == 0; + + if (EmptySubscript) { + auto D = diag(MatchedExpr->getCallee()->getBeginLoc(), + "possibly unsafe 'operator[]'%select{, use safe " + "function '%1() instead|}0") + << FixFunctionEmptyArgs.empty() << FixFunctionEmptyArgs.str() + << MatchedExpr->getCallee()->getSourceRange(); + if (!FixFunctionEmptyArgs.empty()) { + D << FixItHint::CreateInsertion(OCE->getArg(0)->getBeginLoc(), + FixFunctionEmptyArgs.str() + "(") + << FixItHint::CreateRemoval(LeftBracket) + << FixItHint::CreateReplacement(RightBracket, ")"); + } + } else { + diag(MatchedExpr->getCallee()->getBeginLoc(), + "possibly unsafe 'operator[]', use safe function '%0()' instead") + << FixFunction.str() << MatchedExpr->getCallee()->getSourceRange() + << FixItHint::CreateInsertion(OCE->getArg(0)->getBeginLoc(), + FixFunction.str() + "(") + << FixItHint::CreateReplacement(LeftBracket, ", ") + << FixItHint::CreateReplacement(RightBracket, ")"); + } + } + } else if (const auto *MCE = dyn_cast(MatchedExpr)) { + // Case: a.operator[](i) or a->operator[](i) + const auto *Callee = dyn_cast(MCE->getCallee()); + + if (FixMode == At) { + // Cases: a.operator[](i) => a.at(i) and a->operator[](i) => a->at(i) + + const auto *MatchedOperator = + Result.Nodes.getNodeAs("operator"); + + const CXXMethodDecl *Alternative = findAlternativeAt(MatchedOperator); + if (!Alternative) { + diag(Callee->getBeginLoc(), "possibly unsafe 'operator[]', consider " + "bounds-safe alternative 'at()'") + << Callee->getSourceRange(); + return; + } + diag(MatchedExpr->getCallee()->getBeginLoc(), + "possibly unsafe 'operator[]', consider " + "bounds-safe alternative 'at()'") + << FixItHint::CreateReplacement( + SourceRange(Callee->getMemberLoc(), Callee->getEndLoc()), + "at"); + + diag(Alternative->getBeginLoc(), "viable 'at()' defined here", + DiagnosticIDs::Note) + << Alternative->getNameInfo().getSourceRange(); + + } else if (FixMode == Function) { + // Cases: a.operator[](i) => f(a, i) and a->operator[](i) => f(*a, i) + const auto *Callee = dyn_cast(MCE->getCallee()); + + const bool EmptySubscript = + MCE->getMethodDecl()->getNumNonObjectParams() == 0; + + std::string BeginInsertion = + (EmptySubscript ? FixFunctionEmptyArgs.str() : FixFunction.str()) + + "("; + + if (Callee->isArrow()) + BeginInsertion += "*"; + + // Since C++23, the subscript operator may also be called without an + // argument, which makes the following distinction necessary + if (EmptySubscript) { + auto D = diag(MatchedExpr->getCallee()->getBeginLoc(), + "possibly unsafe 'operator[]'%select{, use safe " + "function '%1()' instead|}0") + << FixFunctionEmptyArgs.empty() << FixFunctionEmptyArgs.str() + << Callee->getSourceRange(); + + if (!FixFunctionEmptyArgs.empty()) { + D << FixItHint::CreateInsertion(MatchedExpr->getBeginLoc(), + BeginInsertion) + << FixItHint::CreateRemoval( + SourceRange(Callee->getOperatorLoc(), + MCE->getRParenLoc().getLocWithOffset(-1))); + } + } else { + diag(Callee->getBeginLoc(), + "possibly unsafe 'operator[]', use safe function '%0()' instead") + << FixFunction.str() << Callee->getSourceRange() + << FixItHint::CreateInsertion(MatchedExpr->getBeginLoc(), + BeginInsertion) + << FixItHint::CreateReplacement( + SourceRange( + Callee->getOperatorLoc(), + MCE->getArg(0)->getBeginLoc().getLocWithOffset(-1)), + ", "); + } + } + } +} + +} // namespace clang::tidy::cppcoreguidelines + +namespace clang::tidy { +using P = cppcoreguidelines::ProBoundsAvoidUncheckedContainerAccess; + +llvm::ArrayRef> +OptionEnumMapping::getEnumMapping() { + static constexpr std::pair Mapping[] = { + {P::None, "none"}, {P::At, "at"}, {P::Function, "function"}}; + return {Mapping}; +} +} // namespace clang::tidy diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsAvoidUncheckedContainerAccess.h b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsAvoidUncheckedContainerAccess.h new file mode 100644 index 0000000000000..cfd52d69c0f58 --- /dev/null +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/ProBoundsAvoidUncheckedContainerAccess.h @@ -0,0 +1,56 @@ +//===--- ProBoundsAvoidUncheckedContainerAccess.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_PRO_BOUNDS_AVOID_UNCHECKED_CONTAINER_ACCESS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_AVOID_UNCHECKED_CONTAINER_ACCESS_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::cppcoreguidelines { + +/// Flags calls to operator[] in STL containers and suggests replacing it with +/// safe alternatives. +/// +/// 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/pro-bounds-avoid-unchecked-container-access.html +class ProBoundsAvoidUncheckedContainerAccess : public ClangTidyCheck { +public: + ProBoundsAvoidUncheckedContainerAccess(StringRef Name, + ClangTidyContext *Context); + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + + enum FixModes { None, At, Function }; + +private: + // A list of class names that are excluded from the warning + std::vector ExcludedClasses; + // Setting which fix to suggest + FixModes FixMode; + llvm::StringRef FixFunction; + llvm::StringRef FixFunctionEmptyArgs; +}; +} // namespace clang::tidy::cppcoreguidelines + +namespace clang::tidy { +template <> +struct OptionEnumMapping< + cppcoreguidelines::ProBoundsAvoidUncheckedContainerAccess::FixModes> { + static ArrayRef> + getEnumMapping(); +}; +} // namespace clang::tidy +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_PRO_BOUNDS_AVOID_UNCHECKED_CONTAINER_ACCESS_H diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 85b31bc0b42a6..6a65fc10089d0 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -118,6 +118,13 @@ New checks Detects default initialization (to 0) of variables with ``enum`` type where the enum has no enumerator with value of 0. +- New :doc:`cppcoreguidelines-pro-bounds-avoid-unchecked-container-access + ` + check. + + Finds calls to ``operator[]`` in STL containers and suggests replacing them + with safe alternatives. + - New :doc:`llvm-mlir-op-builder ` check. diff --git a/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/pro-bounds-avoid-unchecked-container-access.rst b/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/pro-bounds-avoid-unchecked-container-access.rst new file mode 100644 index 0000000000000..556d90213b216 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/pro-bounds-avoid-unchecked-container-access.rst @@ -0,0 +1,64 @@ +.. title:: clang-tidy - cppcoreguidelines-pro-bounds-avoid-unchecked-container-access + +cppcoreguidelines-pro-bounds-avoid-unchecked-container-access +============================================================= + +Finds calls to ``operator[]`` in STL containers and suggests replacing them +with safe alternatives. +Safe alternatives include STL ``at`` or GSL ``at`` functions, ``begin()`` or +``end()`` functions, ``range-for`` loops, ``std::span``, or an appropriate +function from ````. + +For example, both + +.. code-block:: c++ + + std::vector a; + int b = a[4]; + +and + +.. code-block:: c++ + + std::unique_ptr a; + int b = a[0]; + +will generate a warning. + +STL containers for which ``operator[]`` is well-defined for all inputs are excluded +from this check (e.g.: ``std::map::operator[]``). + +This check enforces part of the `SL.con.3 +` +guideline and is part of the `Bounds Safety (Bounds 4) +` +profile from the C++ Core Guidelines. + +Options +------- + +.. option:: ExcludeClasses + + Semicolon-delimited list of class names for overwriting the default + exclusion list. The default is: + `::std::map;::std::unordered_map;::std::flat_map`. + +.. option:: FixMode + + Determines what fixes are suggested. Either `none`, `at` (use + ``a.at(index)`` if a fitting function exists) or `function` (use a + function ``f(a, index)``). The default is `none`. + +.. option:: FixFunction + + The function to use in the `function` mode. For C++23 and beyond, the + passed function must support the empty subscript operator, i.e., the case + where ``a[]`` becomes ``f(a)``. :option:`FixFunctionEmptyArgs` can be + used to override the suggested function in that case. The default is `gsl::at`. + +.. option:: FixFunctionEmptyArgs + + The function to use in the `function` mode for the empty subscript operator + case in C++23 and beyond only. If no fixes should be made for empty + subscript operators, pass an empty string. In that case, only the warnings + will be printed. The default is the value of :option:`FixFunction`. diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst index b6444eb3c9aec..d94a0b3bda596 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -201,6 +201,7 @@ Clang-Tidy Checks :doc:`cppcoreguidelines-owning-memory `, :doc:`cppcoreguidelines-prefer-member-initializer `, "Yes" :doc:`cppcoreguidelines-pro-bounds-array-to-pointer-decay `, + :doc:`cppcoreguidelines-pro-bounds-avoid-unchecked-container-access `, "Yes" :doc:`cppcoreguidelines-pro-bounds-constant-array-index `, "Yes" :doc:`cppcoreguidelines-pro-bounds-pointer-arithmetic `, :doc:`cppcoreguidelines-pro-type-const-cast `, diff --git a/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/pro-bounds-avoid-unchecked-container-access.cpp b/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/pro-bounds-avoid-unchecked-container-access.cpp new file mode 100644 index 0000000000000..30d03bd70edf6 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/pro-bounds-avoid-unchecked-container-access.cpp @@ -0,0 +1,341 @@ +// RUN: rm -rf %t && mkdir %t +// RUN: split-file %s %t + + +// RUN: %check_clang_tidy -std=c++11,c++14,c++17,c++20 -check-suffix=DEFAULT %t/cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.cpp \ +// RUN: cppcoreguidelines-pro-bounds-avoid-unchecked-container-access %t -- -- -I%t + +// RUN: %check_clang_tidy -std=c++11,c++14,c++17,c++20 -check-suffix=AT %t/cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.cpp \ +// RUN: cppcoreguidelines-pro-bounds-avoid-unchecked-container-access %t -- \ +// RUN: -config='{CheckOptions: {cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.FixMode: at}}' -- -I%t + +// RUN: %check_clang_tidy -std=c++11,c++14,c++17,c++20 -check-suffix=FUNC %t/cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.cpp \ +// RUN: cppcoreguidelines-pro-bounds-avoid-unchecked-container-access %t -- \ +// RUN: -config='{CheckOptions: {cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.FixMode: function, \ +// RUN: cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.FixFunction: "f"}}' -- -I%t + + +// RUN: %check_clang_tidy -std=c++11,c++14,c++17,c++20 -check-suffix=DEFAULT-NO-EXCL %t/cppcoreguidelines-pro-bounds-avoid-unchecked-container-access-no-excl.cpp \ +// RUN: cppcoreguidelines-pro-bounds-avoid-unchecked-container-access %t -- \ +// RUN: -config='{CheckOptions: {cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.ExcludeClasses: ""}}' -- -I%t + +// RUN: %check_clang_tidy -std=c++11,c++14,c++17,c++20 -check-suffix=AT-NO-EXCL %t/cppcoreguidelines-pro-bounds-avoid-unchecked-container-access-no-excl.cpp \ +// RUN: cppcoreguidelines-pro-bounds-avoid-unchecked-container-access %t -- \ +// RUN: -config='{CheckOptions: {cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.ExcludeClasses: "", \ +// RUN: cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.FixMode: at}}' -- -I%t + +// RUN: %check_clang_tidy -std=c++11,c++14,c++17,c++20 -check-suffix=FUNC-NO-EXCL %t/cppcoreguidelines-pro-bounds-avoid-unchecked-container-access-no-excl.cpp \ +// RUN: cppcoreguidelines-pro-bounds-avoid-unchecked-container-access %t -- \ +// RUN: -config='{CheckOptions: {cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.ExcludeClasses: "", \ +// RUN: cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.FixMode: function, \ +// RUN: cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.FixFunction: "f"}}' -- -I%t + + +// RUN: %check_clang_tidy -std=c++11,c++14,c++17,c++20 -check-suffix=DEFAULT-EXCL %t/cppcoreguidelines-pro-bounds-avoid-unchecked-container-access-excl.cpp \ +// RUN: cppcoreguidelines-pro-bounds-avoid-unchecked-container-access %t -- \ +// RUN: -config='{CheckOptions: {cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.ExcludeClasses: "ExcludedClass1;ExcludedClass2"}}' -- -I%t + +// RUN: %check_clang_tidy -std=c++11,c++14,c++17,c++20 -check-suffix=AT-EXCL %t/cppcoreguidelines-pro-bounds-avoid-unchecked-container-access-excl.cpp \ +// RUN: cppcoreguidelines-pro-bounds-avoid-unchecked-container-access %t -- \ +// RUN: -config='{CheckOptions: {cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.ExcludeClasses: "ExcludedClass1;ExcludedClass2", \ +// RUN: cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.FixMode: at}}' -- -I%t + +// RUN: %check_clang_tidy -std=c++11,c++14,c++17,c++20 -check-suffix=FUNC-EXCL %t/cppcoreguidelines-pro-bounds-avoid-unchecked-container-access-excl.cpp \ +// RUN: cppcoreguidelines-pro-bounds-avoid-unchecked-container-access %t -- \ +// RUN: -config='{CheckOptions: {cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.ExcludeClasses: "ExcludedClass1;ExcludedClass2", \ +// RUN: cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.FixMode: function, \ +// RUN: cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.FixFunction: "f"}}' -- -I%t + + +// RUN: %check_clang_tidy -std=c++23 -check-suffixes=DEFAULT-CXX-23 %t/cppcoreguidelines-pro-bounds-avoid-unchecked-container-access-cxx-23.cpp \ +// RUN: cppcoreguidelines-pro-bounds-avoid-unchecked-container-access %t -- -- -I%t -DCXX_23=1 + +// RUN: %check_clang_tidy -std=c++23 -check-suffixes=AT-CXX-23 %t/cppcoreguidelines-pro-bounds-avoid-unchecked-container-access-cxx-23.cpp \ +// RUN: cppcoreguidelines-pro-bounds-avoid-unchecked-container-access %t -- \ +// RUN: -config='{CheckOptions: {cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.FixMode: at}}' -- -I%t -DCXX_23=1 + +// RUN: %check_clang_tidy -std=c++23 -check-suffixes=FUNC-CXX-23 %t/cppcoreguidelines-pro-bounds-avoid-unchecked-container-access-cxx-23.cpp \ +// RUN: cppcoreguidelines-pro-bounds-avoid-unchecked-container-access %t -- \ +// RUN: -config='{CheckOptions: {cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.FixMode: function, \ +// RUN: cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.FixFunction: "f"}}' -- -I%t -DCXX_23=1 + +// RUN: %check_clang_tidy -std=c++23 -check-suffixes=FUNC-EMPTY-ARGS-CXX-23 %t/cppcoreguidelines-pro-bounds-avoid-unchecked-container-access-cxx-23.cpp \ +// RUN: cppcoreguidelines-pro-bounds-avoid-unchecked-container-access %t -- \ +// RUN: -config='{CheckOptions: {cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.FixMode: function, \ +// RUN: cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.FixFunction: "f", cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.FixFunctionEmptyArgs: "g", }}' -- -I%t -DCXX_23=1 + +// RUN: %check_clang_tidy -std=c++23 -check-suffix=FUNC-EMPTY-ARGS-EMPTY-CXX-23 %t/cppcoreguidelines-pro-bounds-avoid-unchecked-container-access-cxx-23.cpp \ +// RUN: cppcoreguidelines-pro-bounds-avoid-unchecked-container-access %t -- \ +// RUN: -config='{CheckOptions: {cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.FixMode: function, \ +// RUN: cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.FixFunctionEmptyArgs: "", }}' -- -I%t -DCXX_23=1 + +//--- cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.h + +namespace std { + template + struct array { + T operator[](unsigned i) { + return T{1}; + } + T at(unsigned i) { + return T{1}; + } + T at() { + return T{1}; + } + }; + + template + struct map { + T operator[](unsigned i) { + return T{1}; + } + T at(unsigned i) { + return T{1}; + } + }; + + template + struct unique_ptr { + T operator[](unsigned i) { + return T{1}; + } + }; + + template + struct span { + T operator[](unsigned i) { + return T{1}; + } + }; +} // namespace std + +namespace json { + template + struct node{ + T operator[](unsigned i) { + return T{1}; + } + }; +} // namespace json + +//--- cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.cpp + +#include "cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.h" + +struct SubClass : std::array {}; + +template int f(T, unsigned){ return 0;} +template int f(T){ return 0;} + +std::array a; + +auto b = a[0]; +// CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:11: warning: possibly unsafe 'operator[]', consider bounds-safe alternatives [cppcoreguidelines-pro-bounds-avoid-unchecked-container-access] +// CHECK-FIXES-AT: auto b = a.at(0); +// CHECK-FIXES-FUNC: auto b = f(a, 0); + +auto c = a[1+1]; +// CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:11: warning: possibly unsafe 'operator[]', consider bounds-safe alternatives [cppcoreguidelines-pro-bounds-avoid-unchecked-container-access] +// CHECK-FIXES-AT: auto c = a.at(1+1); +// CHECK-FIXES-FUNC: auto c = f(a, 1+1); + +constexpr int Index = 1; + +auto d = a[Index]; +// CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:11: warning: possibly unsafe 'operator[]', consider bounds-safe alternatives [cppcoreguidelines-pro-bounds-avoid-unchecked-container-access] +// CHECK-FIXES-AT: auto d = a.at(Index); +// CHECK-FIXES-FUNC: auto d = f(a, Index); + +int e(int Ind) { + return a[Ind]; + // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:11: warning: possibly unsafe 'operator[]', consider bounds-safe alternatives [cppcoreguidelines-pro-bounds-avoid-unchecked-container-access] + // CHECK-FIXES-AT: return a.at(Ind); + // CHECK-FIXES-FUNC: return f(a, Ind); +} + +auto fa = (&a)->operator[](1); +// CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:11: warning: possibly unsafe 'operator[]', consider bounds-safe alternatives [cppcoreguidelines-pro-bounds-avoid-unchecked-container-access] +// CHECK-FIXES-AT: auto fa = (&a)->at(1); +// CHECK-FIXES-FUNC: auto fa = f(*(&a), 1); + +auto fd = a.operator[](1); +// CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:11: warning: possibly unsafe 'operator[]', consider bounds-safe alternatives [cppcoreguidelines-pro-bounds-avoid-unchecked-container-access] +// CHECK-FIXES-AT: auto fd = a.at(1); +// CHECK-FIXES-FUNC: auto fd = f(a, 1); + + + +auto g = a.at(0); + +std::unique_ptr p; +auto q = p[0]; +// CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:11: warning: possibly unsafe 'operator[]', consider bounds-safe alternatives [cppcoreguidelines-pro-bounds-avoid-unchecked-container-access] +// CHECK-FIXES-AT: auto q = p[0]; +// CHECK-FIXES-FUNC: auto q = f(p, 0); + +std::span s; +auto t = s[0]; +// CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:11: warning: possibly unsafe 'operator[]', consider bounds-safe alternatives [cppcoreguidelines-pro-bounds-avoid-unchecked-container-access] +// CHECK-FIXES-AT: auto t = s[0]; +// CHECK-FIXES-FUNC: auto t = f(s, 0); + +json::node n; +auto m = n[0]; +// CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:11: warning: possibly unsafe 'operator[]', consider bounds-safe alternatives [cppcoreguidelines-pro-bounds-avoid-unchecked-container-access] +// CHECK-FIXES-AT: auto m = n[0]; +// CHECK-FIXES-FUNC: auto m = f(n, 0); + +SubClass Sub; +auto r = Sub[0]; +// CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:13: warning: possibly unsafe 'operator[]', consider bounds-safe alternatives [cppcoreguidelines-pro-bounds-avoid-unchecked-container-access] +// CHECK-FIXES-AT: auto r = Sub.at(0); +// CHECK-FIXES-FUNC: auto r = f(Sub, 0); + +typedef std::array ar; +ar BehindDef; +auto u = BehindDef[0]; +// CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:19: warning: possibly unsafe 'operator[]', consider bounds-safe alternatives [cppcoreguidelines-pro-bounds-avoid-unchecked-container-access] +// CHECK-FIXES-AT: auto u = BehindDef.at(0); +// CHECK-FIXES-FUNC: auto u = f(BehindDef, 0); + +template int TestTemplate(T t){ + return t[0]; + // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:10: warning: possibly unsafe 'operator[]', consider bounds-safe alternatives [cppcoreguidelines-pro-bounds-avoid-unchecked-container-access] + +} + + +auto v = TestTemplate<>(a); +auto w = TestTemplate<>(p); + +#define SUBSCRIPT_BEHIND_MACRO(x) a[x] +#define ARG_BEHIND_MACRO 0 +#define OBJECT_BEHIND_MACRO a + +auto m1 = SUBSCRIPT_BEHIND_MACRO(0); +// CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:11: warning: possibly unsafe 'operator[]', consider bounds-safe alternatives [cppcoreguidelines-pro-bounds-avoid-unchecked-container-access] + +auto m2 = a[ARG_BEHIND_MACRO]; +// CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:12: warning: possibly unsafe 'operator[]', consider bounds-safe alternatives [cppcoreguidelines-pro-bounds-avoid-unchecked-container-access] +// CHECK-FIXES-AT: auto m2 = a.at(ARG_BEHIND_MACRO); +// CHECK-FIXES-FUNC: auto m2 = f(a, ARG_BEHIND_MACRO); + +auto m3 = OBJECT_BEHIND_MACRO[0]; +// CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:30: warning: possibly unsafe 'operator[]', consider bounds-safe alternatives [cppcoreguidelines-pro-bounds-avoid-unchecked-container-access] +// CHECK-FIXES-AT: auto m3 = OBJECT_BEHIND_MACRO.at(0); +// CHECK-FIXES-FUNC: auto m3 = f(OBJECT_BEHIND_MACRO, 0); + +// Check that spacing does not invalidate the fixes +std::array longname; + +auto z1 = longname [ 0 ] ; +// CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:22: warning: possibly unsafe 'operator[]', consider bounds-safe alternatives [cppcoreguidelines-pro-bounds-avoid-unchecked-container-access] +// CHECK-FIXES-AT: auto z1 = longname .at( 0 ) ; +// CHECK-FIXES-FUNC: auto z1 = f(longname , 0 ) ; +auto z2 = longname . operator[] ( 0 ); +// CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:11: warning: possibly unsafe 'operator[]', consider bounds-safe alternatives [cppcoreguidelines-pro-bounds-avoid-unchecked-container-access] +// CHECK-FIXES-AT: auto z2 = longname . at ( 0 ); +// CHECK-FIXES-FUNC: auto z2 = f(longname , 0 ); +auto z3 = (&longname) -> operator[] ( 0 ); +// CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:11: warning: possibly unsafe 'operator[]', consider bounds-safe alternatives [cppcoreguidelines-pro-bounds-avoid-unchecked-container-access] +// CHECK-FIXES-AT: auto z3 = (&longname) -> at ( 0 ); +// CHECK-FIXES-FUNC: auto z3 = f(*(&longname) , 0 ); + + +//--- cppcoreguidelines-pro-bounds-avoid-unchecked-container-access-no-excl.cpp + +#include "cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.h" + +class ExcludedClass1 { + public: + int operator[](unsigned i) { + return 1; + } + int at(unsigned i) { + return 1; + } +}; + +class ExcludedClass2 { + public: + int operator[](unsigned i) { + return 1; + } + int at(unsigned i) { + return 1; + } +}; + +ExcludedClass1 E1; +auto x1 = E1[0]; +// CHECK-MESSAGES-DEFAULT-NO-EXCL: :[[@LINE-1]]:13: warning: possibly unsafe 'operator[]', consider bounds-safe alternatives [cppcoreguidelines-pro-bounds-avoid-unchecked-container-access] +// CHECK-FIXES-AT-NO-EXCL: auto x1 = E1.at(0); +// CHECK-FIXES-FUNC-NO-EXCL: auto x1 = f(E1, 0); + +ExcludedClass2 E2; +auto x2 = E2[0]; +// CHECK-MESSAGES-DEFAULT-NO-EXCL: :[[@LINE-1]]:13: warning: possibly unsafe 'operator[]', consider bounds-safe alternatives [cppcoreguidelines-pro-bounds-avoid-unchecked-container-access] +// CHECK-FIXES-AT-NO-EXCL: auto x2 = E2.at(0); +// CHECK-FIXES-FUNC-NO-EXCL: auto x2 = f(E2, 0); + +std::map TestMapNoExcl; +auto y = TestMapNoExcl[0]; +// CHECK-MESSAGES-DEFAULT-NO-EXCL: :[[@LINE-1]]:23: warning: possibly unsafe 'operator[]', consider bounds-safe alternatives [cppcoreguidelines-pro-bounds-avoid-unchecked-container-access] +// CHECK-FIXES-AT-NO-EXCL: auto y = TestMapNoExcl.at(0); +// CHECK-FIXES-FUNC-NO-EXCL: auto y = f(TestMapNoExcl, 0); + + +//--- cppcoreguidelines-pro-bounds-avoid-unchecked-container-access-excl.cpp + +#include "cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.h" + +std::map TestMapExcl; +auto y = TestMapExcl[0]; +// CHECK-MESSAGES-DEFAULT-EXCL: :[[@LINE-1]]:21: warning: possibly unsafe 'operator[]', consider bounds-safe alternatives [cppcoreguidelines-pro-bounds-avoid-unchecked-container-access] +// CHECK-FIXES-AT-EXCL: auto y = TestMapExcl.at(0); +// CHECK-FIXES-FUNC-EXCL: auto y = f(TestMapExcl, 0); + + +//--- cppcoreguidelines-pro-bounds-avoid-unchecked-container-access-cxx-23.cpp +#ifdef CXX_23 +#include "cppcoreguidelines-pro-bounds-avoid-unchecked-container-access.h" + +namespace std { + template + struct array_cxx_23 { + T operator[]() { + return T{1}; + } + T at() { + return T{1}; + } + }; +}; + +std::array_cxx_23 a; + +auto b23 = a[]; +// CHECK-MESSAGES-DEFAULT-CXX-23: :[[@LINE-1]]:13: warning: possibly unsafe 'operator[]', consider bounds-safe alternatives [cppcoreguidelines-pro-bounds-avoid-unchecked-container-access] +// CHECK-FIXES-AT-CXX-23: auto b23 = a.at(); +// CHECK-FIXES-FUNC-CXX-23: auto b23 = f(a); +// CHECK-FIXES-FUNC-EMPTY-ARGS-CXX-23: auto b23 = g(a); +// CHECK-MESSAGES-FUNC-EMPTY-ARGS-EMPTY-CXX-23: :[[@LINE-5]]:13: warning: possibly unsafe 'operator[]' [cppcoreguidelines-pro-bounds-avoid-unchecked-container-access] +// CHECK-MESSAGES-FUNC-EMPTY-ARGS-EMPTY-CXX-23-NOT: :[[@LINE-6]]:{{.*}}: note: FIX-IT applied suggested code changes + +auto fa23 = (&a)->operator[](); +// CHECK-MESSAGES-DEFAULT-CXX-23: :[[@LINE-1]]:13: warning: possibly unsafe 'operator[]', consider bounds-safe alternatives [cppcoreguidelines-pro-bounds-avoid-unchecked-container-access] +// CHECK-FIXES-AT-CXX-23: auto fa23 = (&a)->at(); +// CHECK-FIXES-FUNC-CXX-23: auto fa23 = f(*(&a)); +// CHECK-FIXES-FUNC-EMPTY-ARGS-CXX-23: auto fa23 = g(*(&a)); +// CHECK-MESSAGES-FUNC-EMPTY-ARGS-EMPTY-CXX-23: :[[@LINE-5]]:13: warning: possibly unsafe 'operator[]' [cppcoreguidelines-pro-bounds-avoid-unchecked-container-access] +// CHECK-MESSAGES-FUNC-EMPTY-ARGS-EMPTY-CXX-23-NOT: :[[@LINE-6]]:{{.*}}: note: FIX-IT applied suggested code changes + +auto fd23 = a.operator[](); +// CHECK-MESSAGES-DEFAULT-CXX-23: :[[@LINE-1]]:13: warning: possibly unsafe 'operator[]', consider bounds-safe alternatives [cppcoreguidelines-pro-bounds-avoid-unchecked-container-access] +// CHECK-FIXES-AT-CXX-23: auto fd23 = a.at(); +// CHECK-FIXES-FUNC-CXX-23: auto fd23 = f(a); +// CHECK-FIXES-FUNC-EMPTY-ARGS-CXX-23: auto fd23 = g(a); +// CHECK-MESSAGES-FUNC-EMPTY-ARGS-EMPTY-CXX-23: :[[@LINE-5]]:13: warning: possibly unsafe 'operator[]' [cppcoreguidelines-pro-bounds-avoid-unchecked-container-access] +// CHECK-MESSAGES-FUNC-EMPTY-ARGS-EMPTY-CXX-23-NOT: :[[@LINE-6]]:{{.*}}: note: FIX-IT applied suggested code changes +#endif