| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| // Test how BOLT handles indirect branch sequence of instructions in | ||
| // AArch64MCPlus builder. | ||
|
|
||
| // clang-format off | ||
|
|
||
| // REQUIRES: system-linux | ||
| // RUN: llvm-mc -filetype=obj -triple aarch64-unknown-unknown %s -o %t.o | ||
| // RUN: %clang %cflags --target=aarch64-unknown-linux %t.o -o %t.exe -Wl,-q | ||
| // RUN: llvm-bolt %t.exe -o %t.bolt --print-cfg --strict\ | ||
| // RUN: -v=1 2>&1 | FileCheck %s | ||
|
|
||
| // Pattern 1: there is no shift amount after the 'add' instruction. | ||
| // | ||
| // adr x6, 0x219fb0 <sigall_set+0x88> | ||
| // add x6, x6, x14, lsl #2 | ||
| // ldr w7, [x6] | ||
| // add x6, x6, w7, sxtw => no shift amount | ||
| // br x6 | ||
| // | ||
|
|
||
| // Pattern 2: nop/adr pair is used in place of adrp/add | ||
| // | ||
| // nop => nop/adr instead of adrp/add | ||
| // adr x13, 0x215a18 <_nl_value_type_LC_COLLATE+0x50> | ||
| // ldrh w13, [x13, w12, uxtw #1] | ||
| // adr x12, 0x247b30 <__gettextparse+0x5b0> | ||
| // add x13, x12, w13, sxth #2 | ||
| // br x13 | ||
|
|
||
| .section .text | ||
| .align 4 | ||
| .globl _start | ||
| .type _start, %function | ||
| _start: | ||
| bl test1 | ||
| bl test2 | ||
| // mov x0, #4 | ||
| // mov w8, #93 | ||
| // svc #0 | ||
|
|
||
| // Pattern 1 | ||
| // CHECK: BOLT-WARNING: Failed to match indirect branch: ShiftVAL != 2 | ||
| .globl test1 | ||
| .type test1, %function | ||
| test1: | ||
| mov x1, #0 | ||
| adr x3, datatable | ||
| add x3, x3, x1, lsl #2 | ||
| ldr w2, [x3] | ||
| add x3, x3, w2, sxtw | ||
| br x3 | ||
| test1_0: | ||
| ret | ||
| test1_1: | ||
| ret | ||
| test1_2: | ||
| ret | ||
|
|
||
| // Pattern 2 | ||
| // CHECK: BOLT-WARNING: Failed to match indirect branch: nop/adr instead of adrp/add | ||
| .globl test2 | ||
| .type test2, %function | ||
| test2: | ||
| nop | ||
| adr x3, jump_table | ||
| ldrh w3, [x3, x1, lsl #1] | ||
| adr x1, test2_0 | ||
| add x3, x1, w3, sxth #2 | ||
| br x3 | ||
| test2_0: | ||
| ret | ||
| test2_1: | ||
| ret | ||
|
|
||
| .section .rodata,"a",@progbits | ||
| datatable: | ||
| .word test1_0-datatable | ||
| .word test1_1-datatable | ||
| .word test1_2-datatable | ||
|
|
||
| jump_table: | ||
| .hword (test2_0-test2_0)>>2 | ||
| .hword (test2_1-test2_0)>>2 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,371 @@ | ||
| //===--- UseRangesCheck.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 "UseRangesCheck.h" | ||
| #include "clang/AST/Decl.h" | ||
| #include "clang/Basic/Diagnostic.h" | ||
| #include "clang/Basic/LLVM.h" | ||
| #include "llvm/ADT/ArrayRef.h" | ||
| #include "llvm/ADT/IntrusiveRefCntPtr.h" | ||
| #include "llvm/ADT/SmallString.h" | ||
| #include "llvm/ADT/SmallVector.h" | ||
| #include "llvm/ADT/StringRef.h" | ||
| #include <initializer_list> | ||
| #include <optional> | ||
| #include <string> | ||
|
|
||
| // FixItHint - Let the docs script know that this class does provide fixits | ||
|
|
||
| namespace clang::tidy::boost { | ||
|
|
||
| namespace { | ||
| /// Base replacer that handles the boost include path and namespace | ||
| class BoostReplacer : public UseRangesCheck::Replacer { | ||
| public: | ||
| BoostReplacer(ArrayRef<UseRangesCheck::Signature> Signatures, | ||
| bool IncludeSystem) | ||
| : Signatures(Signatures), IncludeSystem(IncludeSystem) {} | ||
|
|
||
| ArrayRef<UseRangesCheck::Signature> getReplacementSignatures() const final { | ||
| return Signatures; | ||
| } | ||
|
|
||
| virtual std::pair<StringRef, StringRef> | ||
| getBoostName(const NamedDecl &OriginalName) const = 0; | ||
|
|
||
| virtual std::pair<StringRef, StringRef> | ||
| getBoostHeader(const NamedDecl &OriginalName) const = 0; | ||
|
|
||
| std::optional<std::string> | ||
| getReplaceName(const NamedDecl &OriginalName) const final { | ||
| auto [Namespace, Function] = getBoostName(OriginalName); | ||
| return ("boost::" + Namespace + (Namespace.empty() ? "" : "::") + Function) | ||
| .str(); | ||
| } | ||
|
|
||
| std::optional<std::string> | ||
| getHeaderInclusion(const NamedDecl &OriginalName) const final { | ||
| auto [Path, HeaderName] = getBoostHeader(OriginalName); | ||
| return ((IncludeSystem ? "<boost/" : "boost/") + Path + | ||
| (Path.empty() ? "" : "/") + HeaderName + | ||
| (IncludeSystem ? ".hpp>" : ".hpp")) | ||
| .str(); | ||
| } | ||
|
|
||
| private: | ||
| SmallVector<UseRangesCheck::Signature> Signatures; | ||
| bool IncludeSystem; | ||
| }; | ||
|
|
||
| /// Creates replaces where the header file lives in | ||
| /// `boost/algorithm/<FUNC_NAME>.hpp` and the function is named | ||
| /// `boost::range::<FUNC_NAME>` | ||
| class BoostRangeAlgorithmReplacer : public BoostReplacer { | ||
| public: | ||
| using BoostReplacer::BoostReplacer; | ||
|
|
||
| std::pair<StringRef, StringRef> | ||
| getBoostName(const NamedDecl &OriginalName) const override { | ||
| return {"range", OriginalName.getName()}; | ||
| } | ||
|
|
||
| std::pair<StringRef, StringRef> | ||
| getBoostHeader(const NamedDecl &OriginalName) const override { | ||
| return {"range/algorithm", OriginalName.getName()}; | ||
| } | ||
| }; | ||
|
|
||
| /// Creates replaces where the header file lives in | ||
| /// `boost/algorithm/<CUSTOM_HEADER>.hpp` and the function is named | ||
| /// `boost::range::<FUNC_NAME>` | ||
| class CustomBoostAlgorithmHeaderReplacer : public BoostRangeAlgorithmReplacer { | ||
| public: | ||
| CustomBoostAlgorithmHeaderReplacer( | ||
| StringRef HeaderName, ArrayRef<UseRangesCheck::Signature> Signatures, | ||
| bool IncludeSystem) | ||
| : BoostRangeAlgorithmReplacer(Signatures, IncludeSystem), | ||
| HeaderName(HeaderName) {} | ||
|
|
||
| std::pair<StringRef, StringRef> | ||
| getBoostHeader(const NamedDecl & /*OriginalName*/) const override { | ||
| return {"range/algorithm", HeaderName}; | ||
| } | ||
|
|
||
| private: | ||
| StringRef HeaderName; | ||
| }; | ||
|
|
||
| /// Creates replaces where the header file lives in | ||
| /// `boost/algorithm/<SUB_HEADER>.hpp` and the function is named | ||
| /// `boost::algorithm::<FUNC_NAME>` | ||
| class BoostAlgorithmReplacer : public BoostReplacer { | ||
| public: | ||
| BoostAlgorithmReplacer(StringRef SubHeader, | ||
| ArrayRef<UseRangesCheck::Signature> Signatures, | ||
| bool IncludeSystem) | ||
| : BoostReplacer(Signatures, IncludeSystem), | ||
| SubHeader(("algorithm/" + SubHeader).str()) {} | ||
| std::pair<StringRef, StringRef> | ||
| getBoostName(const NamedDecl &OriginalName) const override { | ||
| return {"algorithm", OriginalName.getName()}; | ||
| } | ||
|
|
||
| std::pair<StringRef, StringRef> | ||
| getBoostHeader(const NamedDecl &OriginalName) const override { | ||
| return {SubHeader, OriginalName.getName()}; | ||
| } | ||
|
|
||
| private: | ||
| std::string SubHeader; | ||
| }; | ||
|
|
||
| /// Creates replaces where the header file lives in | ||
| /// `boost/algorithm/<SUB_HEADER>/<HEADER_NAME>.hpp` and the function is named | ||
| /// `boost::algorithm::<FUNC_NAME>` | ||
| class CustomBoostAlgorithmReplacer : public BoostReplacer { | ||
| public: | ||
| CustomBoostAlgorithmReplacer(StringRef SubHeader, StringRef HeaderName, | ||
| ArrayRef<UseRangesCheck::Signature> Signatures, | ||
| bool IncludeSystem) | ||
| : BoostReplacer(Signatures, IncludeSystem), | ||
| SubHeader(("algorithm/" + SubHeader).str()), HeaderName(HeaderName) {} | ||
| std::pair<StringRef, StringRef> | ||
| getBoostName(const NamedDecl &OriginalName) const override { | ||
| return {"algorithm", OriginalName.getName()}; | ||
| } | ||
|
|
||
| std::pair<StringRef, StringRef> | ||
| getBoostHeader(const NamedDecl & /*OriginalName*/) const override { | ||
| return {SubHeader, HeaderName}; | ||
| } | ||
|
|
||
| private: | ||
| std::string SubHeader; | ||
| StringRef HeaderName; | ||
| }; | ||
|
|
||
| /// A Replacer that is used for functions that just call a new overload | ||
| class MakeOverloadReplacer : public UseRangesCheck::Replacer { | ||
| public: | ||
| explicit MakeOverloadReplacer(ArrayRef<UseRangesCheck::Signature> Signatures) | ||
| : Signatures(Signatures) {} | ||
|
|
||
| ArrayRef<UseRangesCheck::Signature> | ||
| getReplacementSignatures() const override { | ||
| return Signatures; | ||
| } | ||
|
|
||
| std::optional<std::string> | ||
| getReplaceName(const NamedDecl & /* OriginalName */) const override { | ||
| return std::nullopt; | ||
| } | ||
|
|
||
| std::optional<std::string> | ||
| getHeaderInclusion(const NamedDecl & /* OriginalName */) const override { | ||
| return std::nullopt; | ||
| } | ||
|
|
||
| private: | ||
| SmallVector<UseRangesCheck::Signature> Signatures; | ||
| }; | ||
|
|
||
| /// A replacer that replaces functions with an equivalent named function in the | ||
| /// root boost namespace | ||
| class FixedBoostReplace : public BoostReplacer { | ||
| public: | ||
| FixedBoostReplace(StringRef Header, | ||
| ArrayRef<UseRangesCheck::Signature> Signatures, | ||
| bool IncludeBoostSystem) | ||
| : BoostReplacer(Signatures, IncludeBoostSystem), Header(Header) {} | ||
|
|
||
| std::pair<StringRef, StringRef> | ||
| getBoostName(const NamedDecl &OriginalName) const override { | ||
| return {{}, OriginalName.getName()}; | ||
| } | ||
|
|
||
| std::pair<StringRef, StringRef> | ||
| getBoostHeader(const NamedDecl & /* OriginalName */) const override { | ||
| return {{}, Header}; | ||
| } | ||
|
|
||
| private: | ||
| StringRef Header; | ||
| }; | ||
|
|
||
| } // namespace | ||
|
|
||
| utils::UseRangesCheck::ReplacerMap UseRangesCheck::getReplacerMap() const { | ||
|
|
||
| ReplacerMap Results; | ||
| static const Signature SingleSig = {{0}}; | ||
| static const Signature TwoSig = {{0}, {2}}; | ||
| static const auto AddFrom = | ||
| [&Results](llvm::IntrusiveRefCntPtr<UseRangesCheck::Replacer> Replacer, | ||
| std::initializer_list<StringRef> Names, StringRef Prefix) { | ||
| llvm::SmallString<64> Buffer; | ||
| for (const auto &Name : Names) { | ||
| Buffer.assign({"::", Prefix, (Prefix.empty() ? "" : "::"), Name}); | ||
| Results.try_emplace(Buffer, Replacer); | ||
| } | ||
| }; | ||
|
|
||
| static const auto AddFromStd = | ||
| [](llvm::IntrusiveRefCntPtr<UseRangesCheck::Replacer> Replacer, | ||
| std::initializer_list<StringRef> Names) { | ||
| AddFrom(Replacer, Names, "std"); | ||
| }; | ||
|
|
||
| static const auto AddFromBoost = | ||
| [](llvm::IntrusiveRefCntPtr<UseRangesCheck::Replacer> Replacer, | ||
| std::initializer_list< | ||
| std::pair<StringRef, std::initializer_list<StringRef>>> | ||
| NamespaceAndNames) { | ||
| for (auto [Namespace, Names] : NamespaceAndNames) | ||
| AddFrom(Replacer, Names, | ||
| SmallString<64>{"boost", (Namespace.empty() ? "" : "::"), | ||
| Namespace}); | ||
| }; | ||
|
|
||
| AddFromStd(llvm::makeIntrusiveRefCnt<CustomBoostAlgorithmHeaderReplacer>( | ||
| "set_algorithm", TwoSig, IncludeBoostSystem), | ||
| {"includes", "set_union", "set_intersection", "set_difference", | ||
| "set_symmetric_difference"}); | ||
|
|
||
| AddFromStd(llvm::makeIntrusiveRefCnt<BoostRangeAlgorithmReplacer>( | ||
| SingleSig, IncludeBoostSystem), | ||
| {"unique", "lower_bound", "stable_sort", | ||
| "equal_range", "remove_if", "sort", | ||
| "random_shuffle", "remove_copy", "stable_partition", | ||
| "remove_copy_if", "count", "copy_backward", | ||
| "reverse_copy", "adjacent_find", "remove", | ||
| "upper_bound", "binary_search", "replace_copy_if", | ||
| "for_each", "generate", "count_if", | ||
| "min_element", "reverse", "replace_copy", | ||
| "fill", "unique_copy", "transform", | ||
| "copy", "replace", "find", | ||
| "replace_if", "find_if", "partition", | ||
| "max_element"}); | ||
|
|
||
| AddFromStd(llvm::makeIntrusiveRefCnt<BoostRangeAlgorithmReplacer>( | ||
| TwoSig, IncludeBoostSystem), | ||
| {"find_end", "merge", "partial_sort_copy", "find_first_of", | ||
| "search", "lexicographical_compare", "equal", "mismatch"}); | ||
|
|
||
| AddFromStd(llvm::makeIntrusiveRefCnt<CustomBoostAlgorithmHeaderReplacer>( | ||
| "permutation", SingleSig, IncludeBoostSystem), | ||
| {"next_permutation", "prev_permutation"}); | ||
|
|
||
| AddFromStd(llvm::makeIntrusiveRefCnt<CustomBoostAlgorithmHeaderReplacer>( | ||
| "heap_algorithm", SingleSig, IncludeBoostSystem), | ||
| {"push_heap", "pop_heap", "make_heap", "sort_heap"}); | ||
|
|
||
| AddFromStd(llvm::makeIntrusiveRefCnt<BoostAlgorithmReplacer>( | ||
| "cxx11", SingleSig, IncludeBoostSystem), | ||
| {"copy_if", "is_permutation", "is_partitioned", "find_if_not", | ||
| "partition_copy", "any_of", "iota", "all_of", "partition_point", | ||
| "is_sorted", "none_of"}); | ||
|
|
||
| AddFromStd(llvm::makeIntrusiveRefCnt<CustomBoostAlgorithmReplacer>( | ||
| "cxx11", "is_sorted", SingleSig, IncludeBoostSystem), | ||
| {"is_sorted_until"}); | ||
|
|
||
| AddFromStd(llvm::makeIntrusiveRefCnt<FixedBoostReplace>( | ||
| "range/numeric", SingleSig, IncludeBoostSystem), | ||
| {"accumulate", "partial_sum", "adjacent_difference"}); | ||
|
|
||
| if (getLangOpts().CPlusPlus17) | ||
| AddFromStd(llvm::makeIntrusiveRefCnt<BoostAlgorithmReplacer>( | ||
| "cxx17", SingleSig, IncludeBoostSystem), | ||
| {"reduce"}); | ||
|
|
||
| AddFromBoost(llvm::makeIntrusiveRefCnt<MakeOverloadReplacer>(SingleSig), | ||
| {{"algorithm", | ||
| {"reduce", | ||
| "find_backward", | ||
| "find_not_backward", | ||
| "find_if_backward", | ||
| "find_if_not_backward", | ||
| "hex", | ||
| "hex_lower", | ||
| "unhex", | ||
| "is_partitioned_until", | ||
| "is_palindrome", | ||
| "copy_if", | ||
| "copy_while", | ||
| "copy_until", | ||
| "copy_if_while", | ||
| "copy_if_until", | ||
| "is_permutation", | ||
| "is_partitioned", | ||
| "one_of", | ||
| "one_of_equal", | ||
| "find_if_not", | ||
| "partition_copy", | ||
| "any_of", | ||
| "any_of_equal", | ||
| "iota", | ||
| "all_of", | ||
| "all_of_equal", | ||
| "partition_point", | ||
| "is_sorted_until", | ||
| "is_sorted", | ||
| "is_increasing", | ||
| "is_decreasing", | ||
| "is_strictly_increasing", | ||
| "is_strictly_decreasing", | ||
| "none_of", | ||
| "none_of_equal", | ||
| "clamp_range"}}}); | ||
|
|
||
| AddFromBoost( | ||
| llvm::makeIntrusiveRefCnt<MakeOverloadReplacer>(TwoSig), | ||
| {{"algorithm", {"apply_permutation", "apply_reverse_permutation"}}}); | ||
|
|
||
| return Results; | ||
| } | ||
|
|
||
| UseRangesCheck::UseRangesCheck(StringRef Name, ClangTidyContext *Context) | ||
| : utils::UseRangesCheck(Name, Context), | ||
| IncludeBoostSystem(Options.get("IncludeBoostSystem", true)) {} | ||
|
|
||
| void UseRangesCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { | ||
| utils::UseRangesCheck::storeOptions(Opts); | ||
| Options.store(Opts, "IncludeBoostSystem", IncludeBoostSystem); | ||
| } | ||
| DiagnosticBuilder UseRangesCheck::createDiag(const CallExpr &Call) { | ||
| DiagnosticBuilder D = | ||
| diag(Call.getBeginLoc(), "use a %0 version of this algorithm"); | ||
| D << (Call.getDirectCallee()->isInStdNamespace() ? "boost" : "ranged"); | ||
| return D; | ||
| } | ||
| ArrayRef<std::pair<StringRef, StringRef>> | ||
| UseRangesCheck::getFreeBeginEndMethods() const { | ||
| static const std::pair<StringRef, StringRef> Refs[] = { | ||
| {"::std::begin", "::std::end"}, | ||
| {"::std::cbegin", "::std::cend"}, | ||
| {"::boost::range_adl_barrier::begin", "::boost::range_adl_barrier::end"}, | ||
| {"::boost::range_adl_barrier::const_begin", | ||
| "::boost::range_adl_barrier::const_end"}, | ||
| }; | ||
| return Refs; | ||
| } | ||
| std::optional<UseRangesCheck::ReverseIteratorDescriptor> | ||
| UseRangesCheck::getReverseDescriptor() const { | ||
| static const std::pair<StringRef, StringRef> Refs[] = { | ||
| {"::std::rbegin", "::std::rend"}, | ||
| {"::std::crbegin", "::std::crend"}, | ||
| {"::boost::rbegin", "::boost::rend"}, | ||
| {"::boost::const_rbegin", "::boost::const_rend"}, | ||
| }; | ||
| return ReverseIteratorDescriptor{"boost::adaptors::reverse", | ||
| IncludeBoostSystem | ||
| ? "<boost/range/adaptor/reversed.hpp>" | ||
| : "boost/range/adaptor/reversed.hpp", | ||
| Refs}; | ||
| } | ||
| } // namespace clang::tidy::boost |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| //===--- UseRangesCheck.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_BOOST_USERANGESCHECK_H | ||
| #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BOOST_USERANGESCHECK_H | ||
|
|
||
| #include "../utils/UseRangesCheck.h" | ||
|
|
||
| namespace clang::tidy::boost { | ||
|
|
||
| /// Detects calls to standard library iterator algorithms that could be | ||
| /// replaced with a boost ranges version instead | ||
| /// | ||
| /// For the user-facing documentation see: | ||
| /// http://clang.llvm.org/extra/clang-tidy/checks/boost/use-ranges.html | ||
| class UseRangesCheck : public utils::UseRangesCheck { | ||
| public: | ||
| UseRangesCheck(StringRef Name, ClangTidyContext *Context); | ||
|
|
||
| void storeOptions(ClangTidyOptions::OptionMap &Options) override; | ||
|
|
||
| ReplacerMap getReplacerMap() const override; | ||
|
|
||
| DiagnosticBuilder createDiag(const CallExpr &Call) override; | ||
|
|
||
| ArrayRef<std::pair<StringRef, StringRef>> | ||
| getFreeBeginEndMethods() const override; | ||
|
|
||
| std::optional<ReverseIteratorDescriptor> | ||
| getReverseDescriptor() const override; | ||
|
|
||
| private: | ||
| bool IncludeBoostSystem; | ||
| }; | ||
|
|
||
| } // namespace clang::tidy::boost | ||
|
|
||
| #endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BOOST_USERANGESCHECK_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| //===--- PointerArithmeticOnPolymorphicObjectCheck.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 "PointerArithmeticOnPolymorphicObjectCheck.h" | ||
| #include "clang/AST/ASTContext.h" | ||
| #include "clang/ASTMatchers/ASTMatchFinder.h" | ||
|
|
||
| using namespace clang::ast_matchers; | ||
|
|
||
| namespace clang::tidy::bugprone { | ||
|
|
||
| namespace { | ||
| AST_MATCHER(CXXRecordDecl, isAbstract) { return Node.isAbstract(); } | ||
| AST_MATCHER(CXXRecordDecl, isPolymorphic) { return Node.isPolymorphic(); } | ||
| } // namespace | ||
|
|
||
| PointerArithmeticOnPolymorphicObjectCheck:: | ||
| PointerArithmeticOnPolymorphicObjectCheck(StringRef Name, | ||
| ClangTidyContext *Context) | ||
| : ClangTidyCheck(Name, Context), | ||
| IgnoreInheritedVirtualFunctions( | ||
| Options.get("IgnoreInheritedVirtualFunctions", false)) {} | ||
|
|
||
| void PointerArithmeticOnPolymorphicObjectCheck::storeOptions( | ||
| ClangTidyOptions::OptionMap &Opts) { | ||
| Options.store(Opts, "IgnoreInheritedVirtualFunctions", | ||
| IgnoreInheritedVirtualFunctions); | ||
| } | ||
|
|
||
| void PointerArithmeticOnPolymorphicObjectCheck::registerMatchers( | ||
| MatchFinder *Finder) { | ||
| const auto PolymorphicPointerExpr = | ||
| expr(hasType(hasCanonicalType(pointerType(pointee(hasCanonicalType( | ||
| hasDeclaration(cxxRecordDecl(unless(isFinal()), isPolymorphic()) | ||
| .bind("pointee")))))))) | ||
| .bind("pointer"); | ||
|
|
||
| const auto PointerExprWithVirtualMethod = | ||
| expr(hasType(hasCanonicalType( | ||
| pointerType(pointee(hasCanonicalType(hasDeclaration( | ||
| cxxRecordDecl( | ||
| unless(isFinal()), | ||
| anyOf(hasMethod(isVirtualAsWritten()), isAbstract())) | ||
| .bind("pointee")))))))) | ||
| .bind("pointer"); | ||
|
|
||
| const auto SelectedPointerExpr = IgnoreInheritedVirtualFunctions | ||
| ? PointerExprWithVirtualMethod | ||
| : PolymorphicPointerExpr; | ||
|
|
||
| const auto ArraySubscript = arraySubscriptExpr(hasBase(SelectedPointerExpr)); | ||
|
|
||
| const auto BinaryOperators = | ||
| binaryOperator(hasAnyOperatorName("+", "-", "+=", "-="), | ||
| hasEitherOperand(SelectedPointerExpr)); | ||
|
|
||
| const auto UnaryOperators = unaryOperator( | ||
| hasAnyOperatorName("++", "--"), hasUnaryOperand(SelectedPointerExpr)); | ||
|
|
||
| Finder->addMatcher(ArraySubscript, this); | ||
| Finder->addMatcher(BinaryOperators, this); | ||
| Finder->addMatcher(UnaryOperators, this); | ||
| } | ||
|
|
||
| void PointerArithmeticOnPolymorphicObjectCheck::check( | ||
| const MatchFinder::MatchResult &Result) { | ||
| const auto *PointerExpr = Result.Nodes.getNodeAs<Expr>("pointer"); | ||
| const auto *PointeeDecl = Result.Nodes.getNodeAs<CXXRecordDecl>("pointee"); | ||
|
|
||
| diag(PointerExpr->getBeginLoc(), | ||
| "pointer arithmetic on polymorphic object of type %0 can result in " | ||
| "undefined behavior if the dynamic type differs from the pointer type") | ||
| << PointeeDecl << PointerExpr->getSourceRange(); | ||
| } | ||
|
|
||
| } // namespace clang::tidy::bugprone |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| //===--- PointerArithmeticOnPolymorphicObjectCheck.h ------------*- 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_BUGPRONE_POINTERARITHMETICONPOLYMORPHICOBJECTCHECK_H | ||
| #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_POINTERARITHMETICONPOLYMORPHICOBJECTCHECK_H | ||
|
|
||
| #include "../ClangTidyCheck.h" | ||
|
|
||
| namespace clang::tidy::bugprone { | ||
|
|
||
| /// Finds pointer arithmetic performed on classes that contain a | ||
| /// virtual function. | ||
| /// | ||
| /// For the user-facing documentation see: | ||
| /// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/pointer-arithmetic-on-polymorphic-object.html | ||
| class PointerArithmeticOnPolymorphicObjectCheck : public ClangTidyCheck { | ||
| public: | ||
| PointerArithmeticOnPolymorphicObjectCheck(StringRef Name, | ||
| ClangTidyContext *Context); | ||
| void storeOptions(ClangTidyOptions::OptionMap &Opts) override; | ||
| void registerMatchers(ast_matchers::MatchFinder *Finder) override; | ||
| void check(const ast_matchers::MatchFinder::MatchResult &Result) override; | ||
| bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { | ||
| return LangOpts.CPlusPlus; | ||
| } | ||
| std::optional<TraversalKind> getCheckTraversalKind() const override { | ||
| return TK_IgnoreUnlessSpelledInSource; | ||
| } | ||
|
|
||
| private: | ||
| const bool IgnoreInheritedVirtualFunctions; | ||
| }; | ||
|
|
||
| } // namespace clang::tidy::bugprone | ||
|
|
||
| #endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_POINTERARITHMETICONPOLYMORPHICOBJECTCHECK_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,185 @@ | ||
| //===--- UseRangesCheck.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 "UseRangesCheck.h" | ||
| #include "clang/AST/Decl.h" | ||
| #include "llvm/ADT/ArrayRef.h" | ||
| #include "llvm/ADT/IntrusiveRefCntPtr.h" | ||
| #include "llvm/ADT/SmallVector.h" | ||
| #include "llvm/ADT/StringRef.h" | ||
| #include <initializer_list> | ||
|
|
||
| // FixItHint - Let the docs script know that this class does provide fixits | ||
|
|
||
| namespace clang::tidy::modernize { | ||
|
|
||
| static constexpr const char *SingleRangeNames[] = { | ||
| "all_of", | ||
| "any_of", | ||
| "none_of", | ||
| "for_each", | ||
| "find", | ||
| "find_if", | ||
| "find_if_not", | ||
| "adjacent_find", | ||
| "copy", | ||
| "copy_if", | ||
| "copy_backward", | ||
| "move", | ||
| "move_backward", | ||
| "fill", | ||
| "transform", | ||
| "replace", | ||
| "replace_if", | ||
| "generate", | ||
| "remove", | ||
| "remove_if", | ||
| "remove_copy", | ||
| "remove_copy_if", | ||
| "unique", | ||
| "unique_copy", | ||
| "sample", | ||
| "partition_point", | ||
| "lower_bound", | ||
| "upper_bound", | ||
| "equal_range", | ||
| "binary_search", | ||
| "push_heap", | ||
| "pop_heap", | ||
| "make_heap", | ||
| "sort_heap", | ||
| "next_permutation", | ||
| "prev_permutation", | ||
| "reverse", | ||
| "reverse_copy", | ||
| "shift_left", | ||
| "shift_right", | ||
| "is_partitioned", | ||
| "partition", | ||
| "partition_copy", | ||
| "stable_partition", | ||
| "sort", | ||
| "stable_sort", | ||
| "is_sorted", | ||
| "is_sorted_until", | ||
| "is_heap", | ||
| "is_heap_until", | ||
| "max_element", | ||
| "min_element", | ||
| "minmax_element", | ||
| "uninitialized_copy", | ||
| "uninitialized_fill", | ||
| "uninitialized_move", | ||
| "uninitialized_default_construct", | ||
| "uninitialized_value_construct", | ||
| "destroy", | ||
| }; | ||
|
|
||
| static constexpr const char *TwoRangeNames[] = { | ||
| "equal", | ||
| "mismatch", | ||
| "partial_sort_copy", | ||
| "includes", | ||
| "set_union", | ||
| "set_intersection", | ||
| "set_difference", | ||
| "set_symmetric_difference", | ||
| "merge", | ||
| "lexicographical_compare", | ||
| "find_end", | ||
| "search", | ||
| "is_permutation", | ||
| }; | ||
|
|
||
| namespace { | ||
| class StdReplacer : public utils::UseRangesCheck::Replacer { | ||
| public: | ||
| explicit StdReplacer(SmallVector<UseRangesCheck::Signature> Signatures) | ||
| : Signatures(std::move(Signatures)) {} | ||
| std::optional<std::string> | ||
| getReplaceName(const NamedDecl &OriginalName) const override { | ||
| return ("std::ranges::" + OriginalName.getName()).str(); | ||
| } | ||
| ArrayRef<UseRangesCheck::Signature> | ||
| getReplacementSignatures() const override { | ||
| return Signatures; | ||
| } | ||
|
|
||
| private: | ||
| SmallVector<UseRangesCheck::Signature> Signatures; | ||
| }; | ||
|
|
||
| class StdAlgorithmReplacer : public StdReplacer { | ||
| using StdReplacer::StdReplacer; | ||
| std::optional<std::string> | ||
| getHeaderInclusion(const NamedDecl & /*OriginalName*/) const override { | ||
| return "<algorithm>"; | ||
| } | ||
| }; | ||
|
|
||
| class StdNumericReplacer : public StdReplacer { | ||
| using StdReplacer::StdReplacer; | ||
| std::optional<std::string> | ||
| getHeaderInclusion(const NamedDecl & /*OriginalName*/) const override { | ||
| return "<numeric>"; | ||
| } | ||
| }; | ||
| } // namespace | ||
|
|
||
| utils::UseRangesCheck::ReplacerMap UseRangesCheck::getReplacerMap() const { | ||
|
|
||
| utils::UseRangesCheck::ReplacerMap Result; | ||
|
|
||
| // template<typename Iter> Func(Iter first, Iter last,...). | ||
| static const Signature SingleRangeArgs = {{0}}; | ||
| // template<typename Iter1, typename Iter2> | ||
| // Func(Iter1 first1, Iter1 last1, Iter2 first2, Iter2 last2,...). | ||
| static const Signature TwoRangeArgs = {{0}, {2}}; | ||
|
|
||
| static const Signature SingleRangeFunc[] = {SingleRangeArgs}; | ||
|
|
||
| static const Signature TwoRangeFunc[] = {TwoRangeArgs}; | ||
|
|
||
| static const std::pair<ArrayRef<Signature>, ArrayRef<const char *>> | ||
| AlgorithmNames[] = {{SingleRangeFunc, SingleRangeNames}, | ||
| {TwoRangeFunc, TwoRangeNames}}; | ||
| SmallString<64> Buff; | ||
| for (const auto &[Signatures, Values] : AlgorithmNames) { | ||
| auto Replacer = llvm::makeIntrusiveRefCnt<StdAlgorithmReplacer>( | ||
| SmallVector<UseRangesCheck::Signature>{Signatures}); | ||
| for (const auto &Name : Values) { | ||
| Buff.assign({"::std::", Name}); | ||
| Result.try_emplace(Buff, Replacer); | ||
| } | ||
| } | ||
| if (getLangOpts().CPlusPlus23) | ||
| Result.try_emplace( | ||
| "::std::iota", | ||
| llvm::makeIntrusiveRefCnt<StdNumericReplacer>( | ||
| SmallVector<UseRangesCheck::Signature>{std::begin(SingleRangeFunc), | ||
| std::end(SingleRangeFunc)})); | ||
| return Result; | ||
| } | ||
|
|
||
| bool UseRangesCheck::isLanguageVersionSupported( | ||
| const LangOptions &LangOpts) const { | ||
| return LangOpts.CPlusPlus20; | ||
| } | ||
| ArrayRef<std::pair<StringRef, StringRef>> | ||
| UseRangesCheck::getFreeBeginEndMethods() const { | ||
| static const std::pair<StringRef, StringRef> Refs[] = { | ||
| {"::std::begin", "::std::end"}, {"::std::cbegin", "::std::cend"}}; | ||
| return Refs; | ||
| } | ||
| std::optional<UseRangesCheck::ReverseIteratorDescriptor> | ||
| UseRangesCheck::getReverseDescriptor() const { | ||
| static const std::pair<StringRef, StringRef> Refs[] = { | ||
| {"::std::rbegin", "::std::rend"}, {"::std::crbegin", "::std::crend"}}; | ||
| return ReverseIteratorDescriptor{"std::views::reverse", "<ranges>", Refs}; | ||
| } | ||
| } // namespace clang::tidy::modernize |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| //===--- UseRangesCheck.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_MODERNIZE_USERANGESCHECK_H | ||
| #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USERANGESCHECK_H | ||
|
|
||
| #include "../utils/UseRangesCheck.h" | ||
|
|
||
| namespace clang::tidy::modernize { | ||
|
|
||
| /// Detects calls to standard library iterator algorithms that could be | ||
| /// replaced with a ranges version instead | ||
| /// | ||
| /// For the user-facing documentation see: | ||
| /// http://clang.llvm.org/extra/clang-tidy/checks/modernize/use-ranges.html | ||
| class UseRangesCheck : public utils::UseRangesCheck { | ||
| public: | ||
| using utils::UseRangesCheck::UseRangesCheck; | ||
|
|
||
| ReplacerMap getReplacerMap() const override; | ||
|
|
||
| ArrayRef<std::pair<StringRef, StringRef>> | ||
| getFreeBeginEndMethods() const override; | ||
|
|
||
| std::optional<ReverseIteratorDescriptor> | ||
| getReverseDescriptor() const override; | ||
|
|
||
| bool isLanguageVersionSupported(const LangOptions &LangOpts) const override; | ||
| }; | ||
|
|
||
| } // namespace clang::tidy::modernize | ||
|
|
||
| #endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USERANGESCHECK_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,306 @@ | ||
| //===--- UseRangesCheck.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 "UseRangesCheck.h" | ||
| #include "Matchers.h" | ||
| #include "clang/AST/ASTContext.h" | ||
| #include "clang/AST/Decl.h" | ||
| #include "clang/AST/Expr.h" | ||
| #include "clang/ASTMatchers/ASTMatchFinder.h" | ||
| #include "clang/ASTMatchers/ASTMatchers.h" | ||
| #include "clang/ASTMatchers/ASTMatchersInternal.h" | ||
| #include "clang/Basic/Diagnostic.h" | ||
| #include "clang/Basic/LLVM.h" | ||
| #include "clang/Basic/SourceLocation.h" | ||
| #include "clang/Basic/SourceManager.h" | ||
| #include "clang/Lex/Lexer.h" | ||
| #include "llvm/ADT/ArrayRef.h" | ||
| #include "llvm/ADT/STLExtras.h" | ||
| #include "llvm/ADT/SmallBitVector.h" | ||
| #include "llvm/ADT/SmallString.h" | ||
| #include "llvm/ADT/SmallVector.h" | ||
| #include "llvm/ADT/StringRef.h" | ||
| #include "llvm/ADT/Twine.h" | ||
| #include "llvm/Support/raw_ostream.h" | ||
| #include <cassert> | ||
| #include <optional> | ||
| #include <string> | ||
|
|
||
| using namespace clang::ast_matchers; | ||
|
|
||
| static constexpr const char BoundCall[] = "CallExpr"; | ||
| static constexpr const char FuncDecl[] = "FuncDecl"; | ||
| static constexpr const char ArgName[] = "ArgName"; | ||
|
|
||
| namespace clang::tidy::utils { | ||
|
|
||
| static bool operator==(const UseRangesCheck::Indexes &L, | ||
| const UseRangesCheck::Indexes &R) { | ||
| return std::tie(L.BeginArg, L.EndArg, L.ReplaceArg) == | ||
| std::tie(R.BeginArg, R.EndArg, R.ReplaceArg); | ||
| } | ||
|
|
||
| static std::string getFullPrefix(ArrayRef<UseRangesCheck::Indexes> Signature) { | ||
| std::string Output; | ||
| llvm::raw_string_ostream OS(Output); | ||
| for (const UseRangesCheck::Indexes &Item : Signature) | ||
| OS << Item.BeginArg << ":" << Item.EndArg << ":" | ||
| << (Item.ReplaceArg == Item.First ? '0' : '1'); | ||
| return Output; | ||
| } | ||
|
|
||
| static llvm::hash_code hash_value(const UseRangesCheck::Indexes &Indexes) { | ||
| return llvm::hash_combine(Indexes.BeginArg, Indexes.EndArg, | ||
| Indexes.ReplaceArg); | ||
| } | ||
|
|
||
| static llvm::hash_code hash_value(const UseRangesCheck::Signature &Sig) { | ||
| return llvm::hash_combine_range(Sig.begin(), Sig.end()); | ||
| } | ||
|
|
||
| namespace { | ||
|
|
||
| AST_MATCHER(Expr, hasSideEffects) { | ||
| return Node.HasSideEffects(Finder->getASTContext()); | ||
| } | ||
| } // namespace | ||
|
|
||
| static auto | ||
| makeExprMatcher(ast_matchers::internal::Matcher<Expr> ArgumentMatcher, | ||
| ArrayRef<StringRef> MethodNames, | ||
| ArrayRef<StringRef> FreeNames) { | ||
| return expr( | ||
| anyOf(cxxMemberCallExpr(argumentCountIs(0), | ||
| callee(cxxMethodDecl(hasAnyName(MethodNames))), | ||
| on(ArgumentMatcher)), | ||
| callExpr(argumentCountIs(1), hasArgument(0, ArgumentMatcher), | ||
| hasDeclaration(functionDecl(hasAnyName(FreeNames)))))); | ||
| } | ||
|
|
||
| static ast_matchers::internal::Matcher<CallExpr> | ||
| makeMatcherPair(StringRef State, const UseRangesCheck::Indexes &Indexes, | ||
| ArrayRef<StringRef> BeginFreeNames, | ||
| ArrayRef<StringRef> EndFreeNames, | ||
| const std::optional<UseRangesCheck::ReverseIteratorDescriptor> | ||
| &ReverseDescriptor) { | ||
| std::string ArgBound = (ArgName + llvm::Twine(Indexes.BeginArg)).str(); | ||
| SmallString<64> ID = {BoundCall, State}; | ||
| ast_matchers::internal::Matcher<CallExpr> ArgumentMatcher = allOf( | ||
| hasArgument(Indexes.BeginArg, | ||
| makeExprMatcher(expr(unless(hasSideEffects())).bind(ArgBound), | ||
| {"begin", "cbegin"}, BeginFreeNames)), | ||
| hasArgument(Indexes.EndArg, | ||
| makeExprMatcher( | ||
| expr(matchers::isStatementIdenticalToBoundNode(ArgBound)), | ||
| {"end", "cend"}, EndFreeNames))); | ||
| if (ReverseDescriptor) { | ||
| ArgBound.push_back('R'); | ||
| SmallVector<StringRef> RBegin{ | ||
| llvm::make_first_range(ReverseDescriptor->FreeReverseNames)}; | ||
| SmallVector<StringRef> REnd{ | ||
| llvm::make_second_range(ReverseDescriptor->FreeReverseNames)}; | ||
| ArgumentMatcher = anyOf( | ||
| ArgumentMatcher, | ||
| allOf(hasArgument( | ||
| Indexes.BeginArg, | ||
| makeExprMatcher(expr(unless(hasSideEffects())).bind(ArgBound), | ||
| {"rbegin", "crbegin"}, RBegin)), | ||
| hasArgument( | ||
| Indexes.EndArg, | ||
| makeExprMatcher( | ||
| expr(matchers::isStatementIdenticalToBoundNode(ArgBound)), | ||
| {"rend", "crend"}, REnd)))); | ||
| } | ||
| return callExpr(argumentCountAtLeast( | ||
| std::max(Indexes.BeginArg, Indexes.EndArg) + 1), | ||
| ArgumentMatcher) | ||
| .bind(ID); | ||
| } | ||
|
|
||
| void UseRangesCheck::registerMatchers(MatchFinder *Finder) { | ||
| Replaces = getReplacerMap(); | ||
| ReverseDescriptor = getReverseDescriptor(); | ||
| auto BeginEndNames = getFreeBeginEndMethods(); | ||
| llvm::SmallVector<StringRef, 4> BeginNames{ | ||
| llvm::make_first_range(BeginEndNames)}; | ||
| llvm::SmallVector<StringRef, 4> EndNames{ | ||
| llvm::make_second_range(BeginEndNames)}; | ||
| llvm::DenseSet<ArrayRef<Signature>> Seen; | ||
| for (auto I = Replaces.begin(), E = Replaces.end(); I != E; ++I) { | ||
| const ArrayRef<Signature> &Signatures = | ||
| I->getValue()->getReplacementSignatures(); | ||
| if (!Seen.insert(Signatures).second) | ||
| continue; | ||
| assert(!Signatures.empty() && | ||
| llvm::all_of(Signatures, [](auto Index) { return !Index.empty(); })); | ||
| std::vector<StringRef> Names(1, I->getKey()); | ||
| for (auto J = std::next(I); J != E; ++J) | ||
| if (J->getValue()->getReplacementSignatures() == Signatures) | ||
| Names.push_back(J->getKey()); | ||
|
|
||
| std::vector<ast_matchers::internal::DynTypedMatcher> TotalMatchers; | ||
| // As we match on the first matched signature, we need to sort the | ||
| // signatures in order of length(longest to shortest). This way any | ||
| // signature that is a subset of another signature will be matched after the | ||
| // other. | ||
| SmallVector<Signature> SigVec(Signatures); | ||
| llvm::sort(SigVec, [](auto &L, auto &R) { return R.size() < L.size(); }); | ||
| for (const auto &Signature : SigVec) { | ||
| std::vector<ast_matchers::internal::DynTypedMatcher> Matchers; | ||
| for (const auto &ArgPair : Signature) | ||
| Matchers.push_back(makeMatcherPair(getFullPrefix(Signature), ArgPair, | ||
| BeginNames, EndNames, | ||
| ReverseDescriptor)); | ||
| TotalMatchers.push_back( | ||
| ast_matchers::internal::DynTypedMatcher::constructVariadic( | ||
| ast_matchers::internal::DynTypedMatcher::VO_AllOf, | ||
| ASTNodeKind::getFromNodeKind<CallExpr>(), std::move(Matchers))); | ||
| } | ||
| Finder->addMatcher( | ||
| callExpr( | ||
| callee(functionDecl(hasAnyName(std::move(Names))).bind(FuncDecl)), | ||
| ast_matchers::internal::DynTypedMatcher::constructVariadic( | ||
| ast_matchers::internal::DynTypedMatcher::VO_AnyOf, | ||
| ASTNodeKind::getFromNodeKind<CallExpr>(), | ||
| std::move(TotalMatchers)) | ||
| .convertTo<CallExpr>()), | ||
| this); | ||
| } | ||
| } | ||
|
|
||
| static void removeFunctionArgs(DiagnosticBuilder &Diag, const CallExpr &Call, | ||
| ArrayRef<unsigned> Indexes, | ||
| const ASTContext &Ctx) { | ||
| llvm::SmallVector<unsigned> Sorted(Indexes); | ||
| llvm::sort(Sorted); | ||
| // Keep track of commas removed | ||
| llvm::SmallBitVector Commas(Call.getNumArgs()); | ||
| // The first comma is actually the '(' which we can't remove | ||
| Commas[0] = true; | ||
| for (unsigned Index : Sorted) { | ||
| const Expr *Arg = Call.getArg(Index); | ||
| if (Commas[Index]) { | ||
| if (Index >= Commas.size()) { | ||
| Diag << FixItHint::CreateRemoval(Arg->getSourceRange()); | ||
| } else { | ||
| // Remove the next comma | ||
| Commas[Index + 1] = true; | ||
| Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange( | ||
| {Arg->getBeginLoc(), | ||
| Lexer::getLocForEndOfToken( | ||
| Arg->getEndLoc(), 0, Ctx.getSourceManager(), Ctx.getLangOpts()) | ||
| .getLocWithOffset(1)})); | ||
| } | ||
| } else { | ||
| Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange( | ||
| Arg->getBeginLoc().getLocWithOffset(-1), Arg->getEndLoc())); | ||
| Commas[Index] = true; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| void UseRangesCheck::check(const MatchFinder::MatchResult &Result) { | ||
| const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>(FuncDecl); | ||
| std::string Qualified = "::" + Function->getQualifiedNameAsString(); | ||
| auto Iter = Replaces.find(Qualified); | ||
| assert(Iter != Replaces.end()); | ||
| SmallString<64> Buffer; | ||
| for (const Signature &Sig : Iter->getValue()->getReplacementSignatures()) { | ||
| Buffer.assign({BoundCall, getFullPrefix(Sig)}); | ||
| const auto *Call = Result.Nodes.getNodeAs<CallExpr>(Buffer); | ||
| if (!Call) | ||
| continue; | ||
| auto Diag = createDiag(*Call); | ||
| if (auto ReplaceName = Iter->getValue()->getReplaceName(*Function)) | ||
| Diag << FixItHint::CreateReplacement(Call->getCallee()->getSourceRange(), | ||
| *ReplaceName); | ||
| if (auto Include = Iter->getValue()->getHeaderInclusion(*Function)) | ||
| Diag << Inserter.createIncludeInsertion( | ||
| Result.SourceManager->getFileID(Call->getBeginLoc()), *Include); | ||
| llvm::SmallVector<unsigned, 3> ToRemove; | ||
| for (const auto &[First, Second, Replace] : Sig) { | ||
| auto ArgNode = ArgName + std::to_string(First); | ||
| if (const auto *ArgExpr = Result.Nodes.getNodeAs<Expr>(ArgNode)) { | ||
| Diag << FixItHint::CreateReplacement( | ||
| Call->getArg(Replace == Indexes::Second ? Second : First) | ||
| ->getSourceRange(), | ||
| Lexer::getSourceText( | ||
| CharSourceRange::getTokenRange(ArgExpr->getSourceRange()), | ||
| Result.Context->getSourceManager(), | ||
| Result.Context->getLangOpts())); | ||
| } else { | ||
| assert(ReverseDescriptor && "Couldn't find forward argument"); | ||
| ArgNode.push_back('R'); | ||
| ArgExpr = Result.Nodes.getNodeAs<Expr>(ArgNode); | ||
| assert(ArgExpr && "Couldn't find forward or reverse argument"); | ||
| if (ReverseDescriptor->ReverseHeader) | ||
| Diag << Inserter.createIncludeInsertion( | ||
| Result.SourceManager->getFileID(Call->getBeginLoc()), | ||
| *ReverseDescriptor->ReverseHeader); | ||
| Diag << FixItHint::CreateReplacement( | ||
| Call->getArg(Replace == Indexes::Second ? Second : First) | ||
| ->getSourceRange(), | ||
| SmallString<128>{ | ||
| ReverseDescriptor->ReverseAdaptorName, "(", | ||
| Lexer::getSourceText( | ||
| CharSourceRange::getTokenRange(ArgExpr->getSourceRange()), | ||
| Result.Context->getSourceManager(), | ||
| Result.Context->getLangOpts()), | ||
| ")"}); | ||
| } | ||
| ToRemove.push_back(Replace == Indexes::Second ? First : Second); | ||
| } | ||
| removeFunctionArgs(Diag, *Call, ToRemove, *Result.Context); | ||
| return; | ||
| } | ||
| llvm_unreachable("No valid signature found"); | ||
| } | ||
|
|
||
| bool UseRangesCheck::isLanguageVersionSupported( | ||
| const LangOptions &LangOpts) const { | ||
| return LangOpts.CPlusPlus11; | ||
| } | ||
|
|
||
| UseRangesCheck::UseRangesCheck(StringRef Name, ClangTidyContext *Context) | ||
| : ClangTidyCheck(Name, Context), | ||
| Inserter(Options.getLocalOrGlobal("IncludeStyle", | ||
| utils::IncludeSorter::IS_LLVM), | ||
| areDiagsSelfContained()) {} | ||
|
|
||
| void UseRangesCheck::registerPPCallbacks(const SourceManager &, | ||
| Preprocessor *PP, Preprocessor *) { | ||
| Inserter.registerPreprocessor(PP); | ||
| } | ||
|
|
||
| void UseRangesCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { | ||
| Options.store(Opts, "IncludeStyle", Inserter.getStyle()); | ||
| } | ||
|
|
||
| std::optional<std::string> | ||
| UseRangesCheck::Replacer::getHeaderInclusion(const NamedDecl &) const { | ||
| return std::nullopt; | ||
| } | ||
|
|
||
| DiagnosticBuilder UseRangesCheck::createDiag(const CallExpr &Call) { | ||
| return diag(Call.getBeginLoc(), "use a ranges version of this algorithm"); | ||
| } | ||
|
|
||
| std::optional<UseRangesCheck::ReverseIteratorDescriptor> | ||
| UseRangesCheck::getReverseDescriptor() const { | ||
| return std::nullopt; | ||
| } | ||
|
|
||
| ArrayRef<std::pair<StringRef, StringRef>> | ||
| UseRangesCheck::getFreeBeginEndMethods() const { | ||
| return {}; | ||
| } | ||
|
|
||
| std::optional<TraversalKind> UseRangesCheck::getCheckTraversalKind() const { | ||
| return TK_IgnoreUnlessSpelledInSource; | ||
| } | ||
| } // namespace clang::tidy::utils |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| //===--- UseRangesCheck.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_USERANGESCHECK_H | ||
| #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_USERANGESCHECK_H | ||
|
|
||
| #include "../ClangTidyCheck.h" | ||
| #include "IncludeInserter.h" | ||
| #include "clang/AST/Decl.h" | ||
| #include "clang/AST/Expr.h" | ||
| #include "clang/Basic/Diagnostic.h" | ||
| #include "llvm/ADT/IntrusiveRefCntPtr.h" | ||
| #include "llvm/ADT/StringMap.h" | ||
| #include "llvm/ADT/StringRef.h" | ||
| #include <optional> | ||
|
|
||
| namespace clang::tidy::utils { | ||
|
|
||
| /// Base class for handling converting std iterator algorithms to a range | ||
| /// equivalent. | ||
| class UseRangesCheck : public ClangTidyCheck { | ||
| public: | ||
| struct Indexes { | ||
| enum Replace { First, Second }; | ||
| unsigned BeginArg; | ||
| unsigned EndArg = BeginArg + 1; | ||
| Replace ReplaceArg = First; | ||
| }; | ||
|
|
||
| using Signature = SmallVector<Indexes, 2>; | ||
|
|
||
| struct ReverseIteratorDescriptor { | ||
| StringRef ReverseAdaptorName; | ||
| std::optional<StringRef> ReverseHeader; | ||
| ArrayRef<std::pair<StringRef, StringRef>> FreeReverseNames; | ||
| }; | ||
|
|
||
| class Replacer : public llvm::RefCountedBase<Replacer> { | ||
| public: | ||
| /// Gets the name to replace a function with, return std::nullopt for a | ||
| /// replacement where we just call a different overload. | ||
| virtual std::optional<std::string> | ||
| getReplaceName(const NamedDecl &OriginalName) const = 0; | ||
|
|
||
| /// Gets the header needed to access the replaced function | ||
| /// Return std::nullopt if no new header is needed. | ||
| virtual std::optional<std::string> | ||
| getHeaderInclusion(const NamedDecl &OriginalName) const; | ||
|
|
||
| /// Gets an array of all the possible overloads for a function with indexes | ||
| /// where begin and end arguments are. | ||
| virtual ArrayRef<Signature> getReplacementSignatures() const = 0; | ||
| virtual ~Replacer() = default; | ||
| }; | ||
|
|
||
| using ReplacerMap = llvm::StringMap<llvm::IntrusiveRefCntPtr<Replacer>>; | ||
|
|
||
| UseRangesCheck(StringRef Name, ClangTidyContext *Context); | ||
| /// Gets a map of function to replace and methods to create the replacements | ||
| virtual ReplacerMap getReplacerMap() const = 0; | ||
| /// Create a diagnostic for the CallExpr | ||
| /// Override this to support custom diagnostic messages | ||
| virtual DiagnosticBuilder createDiag(const CallExpr &Call); | ||
|
|
||
| virtual std::optional<ReverseIteratorDescriptor> getReverseDescriptor() const; | ||
|
|
||
| /// Gets the fully qualified names of begin and end functions. | ||
| /// The functions must take the container as their one and only argument | ||
| /// `::std::begin` and `::std::end` are a common example | ||
| virtual ArrayRef<std::pair<StringRef, StringRef>> | ||
| getFreeBeginEndMethods() const; | ||
|
|
||
| void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, | ||
| Preprocessor *ModuleExpanderPP) final; | ||
| void registerMatchers(ast_matchers::MatchFinder *Finder) final; | ||
| void check(const ast_matchers::MatchFinder::MatchResult &Result) final; | ||
| bool isLanguageVersionSupported(const LangOptions &LangOpts) const override; | ||
| void storeOptions(ClangTidyOptions::OptionMap &Options) override; | ||
| std::optional<TraversalKind> getCheckTraversalKind() const override; | ||
|
|
||
| private: | ||
| ReplacerMap Replaces; | ||
| std::optional<ReverseIteratorDescriptor> ReverseDescriptor; | ||
| IncludeInserter Inserter; | ||
| }; | ||
|
|
||
| } // namespace clang::tidy::utils | ||
|
|
||
| #endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_USERANGESCHECK_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| .. title:: clang-tidy - boost-use-ranges | ||
|
|
||
| boost-use-ranges | ||
| ================ | ||
|
|
||
| Detects calls to standard library iterator algorithms that could be replaced | ||
| with a Boost ranges version instead. | ||
|
|
||
| Example | ||
| ------- | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| auto Iter1 = std::find(Items.begin(), Items.end(), 0); | ||
| auto AreSame = std::equal(Items1.cbegin(), Items1.cend(), std::begin(Items2), | ||
| std::end(Items2)); | ||
|
|
||
|
|
||
| transforms to: | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| auto Iter1 = boost::range::find(Items, 0); | ||
| auto AreSame = boost::range::equal(Items1, Items2); | ||
|
|
||
| Calls to the following std library algorithms are checked: | ||
| ``includes``,``set_union``,``set_intersection``,``set_difference``, | ||
| ``set_symmetric_difference``,``unique``,``lower_bound``,``stable_sort``, | ||
| ``equal_range``,``remove_if``,``sort``,``random_shuffle``,``remove_copy``, | ||
| ``stable_partition``,``remove_copy_if``,``count``,``copy_backward``, | ||
| ``reverse_copy``,``adjacent_find``,``remove``,``upper_bound``,``binary_search``, | ||
| ``replace_copy_if``,``for_each``,``generate``,``count_if``,``min_element``, | ||
| ``reverse``,``replace_copy``,``fill``,``unique_copy``,``transform``,``copy``, | ||
| ``replace``,``find``,``replace_if``,``find_if``,``partition``,``max_element``, | ||
| ``find_end``,``merge``,``partial_sort_copy``,``find_first_of``,``search``, | ||
| ``lexicographical_compare``,``equal``,``mismatch``,``next_permutation``, | ||
| ``prev_permutation``,``push_heap``,``pop_heap``,``make_heap``,``sort_heap``, | ||
| ``copy_if``,``is_permutation``,``is_partitioned``,``find_if_not``, | ||
| ``partition_copy``,``any_of``,``iota``,``all_of``,``partition_point``, | ||
| ``is_sorted``,``none_of``,``is_sorted_until``,``reduce``,``accumulate``, | ||
| ``parital_sum``,``adjacent_difference``. | ||
|
|
||
| The check will also look for the following functions from the | ||
| ``boost::algorithm`` namespace: | ||
| ``reduce``,``find_backward``,``find_not_backward``,``find_if_backward``, | ||
| ``find_if_not_backward``,``hex``,``hex_lower``,``unhex``, | ||
| ``is_partitioned_until``,``is_palindrome``,``copy_if``,``copy_while``, | ||
| ``copy_until``,``copy_if_while``,``copy_if_until``,``is_permutation``, | ||
| ``is_partitioned``,``one_of``,``one_of_equal``,``find_if_not``, | ||
| ``partition_copy``,``any_of``,``any_of_equal``,``iota``,``all_of``, | ||
| ``all_of_equal``,``partition_point``,``is_sorted_until``,``is_sorted``, | ||
| ``is_increasing``,``is_decreasing``,``is_strictly_increasing``, | ||
| ``is_strictly_decreasing``,``none_of``,``none_of_equal``,``clamp_range``, | ||
| ``apply_permutation``,``apply_reverse_permutation``. | ||
|
|
||
| Reverse Iteration | ||
| ----------------- | ||
|
|
||
| If calls are made using reverse iterators on containers, The code will be | ||
| fixed using the ``boost::adaptors::reverse`` adaptor. | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| auto AreSame = std::equal(Items1.rbegin(), Items1.rend(), | ||
| std::crbegin(Items2), std::crend(Items2)); | ||
|
|
||
| transformst to: | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| auto AreSame = std::equal(boost::adaptors::reverse(Items1), | ||
| boost::adaptors::reverse(Items2)); | ||
|
|
||
| Options | ||
| ------- | ||
|
|
||
| .. option:: IncludeStyle | ||
|
|
||
| A string specifying which include-style is used, `llvm` or `google`. Default | ||
| is `llvm`. | ||
|
|
||
| .. option:: IncludeBoostSystem | ||
|
|
||
| If `true` (default value) the boost headers are included as system headers | ||
| with angle brackets (`#include <boost.hpp>`), otherwise quotes are used | ||
| (`#include "boost.hpp"`). |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| .. title:: clang-tidy - bugprone-pointer-arithmetic-on-polymorphic-object | ||
|
|
||
| bugprone-pointer-arithmetic-on-polymorphic-object | ||
| ================================================= | ||
|
|
||
| Finds pointer arithmetic performed on classes that contain a virtual function. | ||
|
|
||
| Pointer arithmetic on polymorphic objects where the pointer's static type is | ||
| different from its dynamic type is undefined behavior, as the two types could | ||
| have different sizes, and thus the vtable pointer could point to an | ||
| invalid address. | ||
|
|
||
| Finding pointers where the static type contains a virtual member function is a | ||
| good heuristic, as the pointer is likely to point to a different, | ||
| derived object. | ||
|
|
||
| Example: | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| struct Base { | ||
| virtual void ~Base(); | ||
| }; | ||
|
|
||
| struct Derived : public Base {}; | ||
|
|
||
| void foo() { | ||
| Base *b = new Derived[10]; | ||
| b += 1; | ||
| // warning: pointer arithmetic on class that declares a virtual function can | ||
| // result in undefined behavior if the dynamic type differs from the | ||
| // pointer type | ||
|
|
||
| delete[] static_cast<Derived*>(b); | ||
| } | ||
|
|
||
| Options | ||
| ------- | ||
|
|
||
| .. option:: IgnoreInheritedVirtualFunctions | ||
|
|
||
| When `true`, objects that only inherit a virtual function are not checked. | ||
| Classes that do not declare a new virtual function are excluded | ||
| by default, as they make up the majority of false positives. | ||
| Default: `false`. | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| void bar() { | ||
| Base *b = new Base[10]; | ||
| b += 1; // warning, as Base declares a virtual destructor | ||
| delete[] b; | ||
|
|
||
| Derived *d = new Derived[10]; // Derived overrides the destructor, and | ||
| // declares no other virtual functions | ||
| d += 1; // warning only if IgnoreVirtualDeclarationsOnly is set to false | ||
| delete[] d; | ||
| } | ||
|
|
||
| References | ||
| ---------- | ||
|
|
||
| This check corresponds to the SEI Cert rule | ||
| `CTR56-CPP. Do not use pointer arithmetic on polymorphic objects | ||
| <https://wiki.sei.cmu.edu/confluence/display/cplusplus/CTR56-CPP.+Do+not+use+pointer+arithmetic+on+polymorphic+objects>`_. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| .. title:: clang-tidy - cert-ctr56-cpp | ||
| .. meta:: | ||
| :http-equiv=refresh: 5;URL=../bugprone/pointer-arithmetic-on-polymorphic-object.html | ||
|
|
||
| cert-ctr56-cpp | ||
| ============== | ||
|
|
||
| The `cert-ctr56-cpp` check is an alias, please see | ||
| :doc:`bugprone-pointer-arithmetic-on-polymorphic-object | ||
| <../bugprone/pointer-arithmetic-on-polymorphic-object>` for more information. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| .. title:: clang-tidy - modernize-use-ranges | ||
|
|
||
| modernize-use-ranges | ||
| ==================== | ||
|
|
||
| Detects calls to standard library iterator algorithms that could be replaced | ||
| with a ranges version instead. | ||
|
|
||
| Example | ||
| ------- | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| auto Iter1 = std::find(Items.begin(), Items.end(), 0); | ||
| auto AreSame = std::equal(Items1.cbegin(), Items1.cend(), | ||
| std::begin(Items2), std::end(Items2)); | ||
|
|
||
|
|
||
| transforms to: | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| auto Iter1 = std::ranges::find(Items, 0); | ||
| auto AreSame = std::ranges::equal(Items1, Items2); | ||
|
|
||
| Calls to the following std library algorithms are checked: | ||
| ``::std::all_of``,``::std::any_of``,``::std::none_of``,``::std::for_each``, | ||
| ``::std::find``,``::std::find_if``,``::std::find_if_not``, | ||
| ``::std::adjacent_find``,``::std::copy``,``::std::copy_if``, | ||
| ``::std::copy_backward``,``::std::move``,``::std::move_backward``, | ||
| ``::std::fill``,``::std::transform``,``::std::replace``,``::std::replace_if``, | ||
| ``::std::generate``,``::std::remove``,``::std::remove_if``, | ||
| ``::std::remove_copy``,``::std::remove_copy_if``,``::std::unique``, | ||
| ``::std::unique_copy``,``::std::sample``,``::std::partition_point``, | ||
| ``::std::lower_bound``,``::std::upper_bound``,``::std::equal_range``, | ||
| ``::std::binary_search``,``::std::push_heap``,``::std::pop_heap``, | ||
| ``::std::make_heap``,``::std::sort_heap``,``::std::next_permutation``, | ||
| ``::std::prev_permutation``,``::std::iota``,``::std::reverse``, | ||
| ``::std::reverse_copy``,``::std::shift_left``,``::std::shift_right``, | ||
| ``::std::is_partitioned``,``::std::partition``,``::std::partition_copy``, | ||
| ``::std::stable_partition``,``::std::sort``,``::std::stable_sort``, | ||
| ``::std::is_sorted``,``::std::is_sorted_until``,``::std::is_heap``, | ||
| ``::std::is_heap_until``,``::std::max_element``,``::std::min_element``, | ||
| ``::std::minmax_element``,``::std::uninitialized_copy``, | ||
| ``::std::uninitialized_fill``,``::std::uninitialized_move``, | ||
| ``::std::uninitialized_default_construct``, | ||
| ``::std::uninitialized_value_construct``,``::std::destroy``, | ||
| ``::std::partial_sort_copy``,``::std::includes``, | ||
| ``::std::set_union``,``::std::set_intersection``,``::std::set_difference``, | ||
| ``::std::set_symmetric_difference``,``::std::merge``, | ||
| ``::std::lexicographical_compare``,``::std::find_end``,``::std::search``, | ||
| ``::std::is_permutation``,``::std::equal``,``::std::mismatch``. | ||
|
|
||
| Reverse Iteration | ||
| ----------------- | ||
|
|
||
| If calls are made using reverse iterators on containers, The code will be | ||
| fixed using the ``std::views::reverse`` adaptor. | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| auto AreSame = std::equal(Items1.rbegin(), Items1.rend(), | ||
| std::crbegin(Items2), std::crend(Items2)); | ||
|
|
||
| transformst to: | ||
|
|
||
| .. code-block:: c++ | ||
|
|
||
| auto AreSame = std::equal(std::views::reverse(Items1), | ||
| std::views::reverse(Items2)); | ||
|
|
||
| Options | ||
| ------- | ||
|
|
||
| .. option:: IncludeStyle | ||
|
|
||
| A string specifying which include-style is used, `llvm` or `google`. Default | ||
| is `llvm`. | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,194 @@ | ||
| // RUN: %check_clang_tidy -std=c++14 %s boost-use-ranges %t | ||
| // RUN: %check_clang_tidy -std=c++17 %s boost-use-ranges %t -check-suffixes=,CPP17 | ||
|
|
||
| // CHECK-FIXES: #include <boost/range/algorithm/find.hpp> | ||
| // CHECK-FIXES: #include <boost/range/algorithm/reverse.hpp> | ||
| // CHECK-FIXES: #include <boost/range/algorithm/set_algorithm.hpp> | ||
| // CHECK-FIXES: #include <boost/range/algorithm/equal.hpp> | ||
| // CHECK-FIXES: #include <boost/range/algorithm/permutation.hpp> | ||
| // CHECK-FIXES: #include <boost/range/algorithm/heap_algorithm.hpp> | ||
| // CHECK-FIXES: #include <boost/algorithm/cxx11/copy_if.hpp> | ||
| // CHECK-FIXES: #include <boost/algorithm/cxx11/is_sorted.hpp> | ||
| // CHECK-FIXES-CPP17: #include <boost/algorithm/cxx17/reduce.hpp> | ||
| // CHECK-FIXES: #include <boost/range/adaptor/reversed.hpp> | ||
| // CHECK-FIXES: #include <boost/range/numeric.hpp> | ||
|
|
||
| namespace std { | ||
|
|
||
| template <typename T> class vector { | ||
| public: | ||
| using iterator = T *; | ||
| using const_iterator = const T *; | ||
| constexpr const_iterator begin() const; | ||
| constexpr const_iterator end() const; | ||
| constexpr const_iterator cbegin() const; | ||
| constexpr const_iterator cend() const; | ||
| constexpr iterator begin(); | ||
| constexpr iterator end(); | ||
| }; | ||
|
|
||
| template <typename Container> constexpr auto begin(const Container &Cont) { | ||
| return Cont.begin(); | ||
| } | ||
|
|
||
| template <typename Container> constexpr auto begin(Container &Cont) { | ||
| return Cont.begin(); | ||
| } | ||
|
|
||
| template <typename Container> constexpr auto end(const Container &Cont) { | ||
| return Cont.end(); | ||
| } | ||
|
|
||
| template <typename Container> constexpr auto end(Container &Cont) { | ||
| return Cont.end(); | ||
| } | ||
|
|
||
| template <typename Container> constexpr auto cbegin(const Container &Cont) { | ||
| return Cont.cbegin(); | ||
| } | ||
|
|
||
| template <typename Container> constexpr auto cend(const Container &Cont) { | ||
| return Cont.cend(); | ||
| } | ||
| // Find | ||
| template< class InputIt, class T > | ||
| InputIt find(InputIt first, InputIt last, const T& value); | ||
|
|
||
| template <typename Iter> void reverse(Iter begin, Iter end); | ||
|
|
||
| template <class InputIt1, class InputIt2> | ||
| bool includes(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2); | ||
|
|
||
| template <class ForwardIt1, class ForwardIt2> | ||
| bool is_permutation(ForwardIt1 first1, ForwardIt1 last1, ForwardIt2 first2, | ||
| ForwardIt2 last2); | ||
|
|
||
| template <class BidirIt> | ||
| bool next_permutation(BidirIt first, BidirIt last); | ||
|
|
||
| template <class ForwardIt1, class ForwardIt2> | ||
| bool equal(ForwardIt1 first1, ForwardIt1 last1, | ||
| ForwardIt2 first2, ForwardIt2 last2); | ||
|
|
||
| template <class RandomIt> | ||
| void push_heap(RandomIt first, RandomIt last); | ||
|
|
||
| template <class InputIt, class OutputIt, class UnaryPred> | ||
| OutputIt copy_if(InputIt first, InputIt last, OutputIt d_first, UnaryPred pred); | ||
|
|
||
| template <class ForwardIt> | ||
| ForwardIt is_sorted_until(ForwardIt first, ForwardIt last); | ||
|
|
||
| template <class InputIt> | ||
| void reduce(InputIt first, InputIt last); | ||
|
|
||
| template <class InputIt, class T> | ||
| T reduce(InputIt first, InputIt last, T init); | ||
|
|
||
| template <class InputIt, class T, class BinaryOp> | ||
| T reduce(InputIt first, InputIt last, T init, BinaryOp op) { | ||
| // Need a definition to suppress undefined_internal_type when invoked with lambda | ||
| return init; | ||
| } | ||
|
|
||
| template <class InputIt, class T> | ||
| T accumulate(InputIt first, InputIt last, T init); | ||
|
|
||
| } // namespace std | ||
|
|
||
| namespace boost { | ||
| namespace range_adl_barrier { | ||
| template <typename T> void *begin(T &); | ||
| template <typename T> void *end(T &); | ||
| template <typename T> void *const_begin(const T &); | ||
| template <typename T> void *const_end(const T &); | ||
| } // namespace range_adl_barrier | ||
| using namespace range_adl_barrier; | ||
|
|
||
| template <typename T> void *rbegin(T &); | ||
| template <typename T> void *rend(T &); | ||
|
|
||
| template <typename T> void *const_rbegin(T &); | ||
| template <typename T> void *const_rend(T &); | ||
| namespace algorithm { | ||
|
|
||
| template <class InputIterator, class T, class BinaryOperation> | ||
| T reduce(InputIterator first, InputIterator last, T init, BinaryOperation bOp) { | ||
| return init; | ||
| } | ||
| } // namespace algorithm | ||
| } // namespace boost | ||
|
|
||
| bool returnTrue(int val) { | ||
| return true; | ||
| } | ||
|
|
||
| void stdLib() { | ||
| std::vector<int> I, J; | ||
| std::find(I.begin(), I.end(), 0); | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a boost version of this algorithm | ||
| // CHECK-FIXES: boost::range::find(I, 0); | ||
|
|
||
| std::reverse(I.cbegin(), I.cend()); | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a boost version of this algorithm | ||
| // CHECK-FIXES: boost::range::reverse(I); | ||
|
|
||
| std::includes(I.begin(), I.end(), std::begin(J), std::end(J)); | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a boost version of this algorithm | ||
| // CHECK-FIXES: boost::range::includes(I, J); | ||
|
|
||
| std::equal(std::cbegin(I), std::cend(I), J.begin(), J.end()); | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a boost version of this algorithm | ||
| // CHECK-FIXES: boost::range::equal(I, J); | ||
|
|
||
| std::next_permutation(I.begin(), I.end()); | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a boost version of this algorithm | ||
| // CHECK-FIXES: boost::range::next_permutation(I); | ||
|
|
||
| std::push_heap(I.begin(), I.end()); | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a boost version of this algorithm | ||
| // CHECK-FIXES: boost::range::push_heap(I); | ||
|
|
||
| std::copy_if(I.begin(), I.end(), J.begin(), &returnTrue); | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a boost version of this algorithm | ||
| // CHECK-FIXES: boost::algorithm::copy_if(I, J.begin(), &returnTrue); | ||
|
|
||
| std::is_sorted_until(I.begin(), I.end()); | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a boost version of this algorithm | ||
| // CHECK-FIXES: boost::algorithm::is_sorted_until(I); | ||
|
|
||
| std::reduce(I.begin(), I.end()); | ||
| // CHECK-MESSAGES-CPP17: :[[@LINE-1]]:3: warning: use a boost version of this algorithm | ||
| // CHECK-FIXES-CPP17: boost::algorithm::reduce(I); | ||
|
|
||
| std::reduce(I.begin(), I.end(), 2); | ||
| // CHECK-MESSAGES-CPP17: :[[@LINE-1]]:3: warning: use a boost version of this algorithm | ||
| // CHECK-FIXES-CPP17: boost::algorithm::reduce(I, 2); | ||
|
|
||
| std::reduce(I.begin(), I.end(), 0, [](int a, int b){ return a + b; }); | ||
| // CHECK-MESSAGES-CPP17: :[[@LINE-1]]:3: warning: use a boost version of this algorithm | ||
| // CHECK-FIXES-CPP17: boost::algorithm::reduce(I, 0, [](int a, int b){ return a + b; }); | ||
|
|
||
| std::equal(boost::rbegin(I), boost::rend(I), J.begin(), J.end()); | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a boost version of this algorithm | ||
| // CHECK-FIXES: boost::range::equal(boost::adaptors::reverse(I), J); | ||
|
|
||
| std::accumulate(I.begin(), I.end(), 0); | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a boost version of this algorithm | ||
| // CHECK-FIXES: boost::accumulate(I, 0); | ||
| } | ||
|
|
||
| void boostLib() { | ||
| std::vector<int> I; | ||
| boost::algorithm::reduce(I.begin(), I.end(), 0, [](int a, int b){ return a + b; }); | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranged version of this algorithm | ||
| // CHECK-FIXES: boost::algorithm::reduce(I, 0, [](int a, int b){ return a + b; }); | ||
|
|
||
| boost::algorithm::reduce(boost::begin(I), boost::end(I), 1, [](int a, int b){ return a + b; }); | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranged version of this algorithm | ||
| // CHECK-FIXES: boost::algorithm::reduce(I, 1, [](int a, int b){ return a + b; }); | ||
|
|
||
| boost::algorithm::reduce(boost::const_begin(I), boost::const_end(I), 2, [](int a, int b){ return a + b; }); | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranged version of this algorithm | ||
| // CHECK-FIXES: boost::algorithm::reduce(I, 2, [](int a, int b){ return a + b; }); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| // RUN: %check_clang_tidy %s bugprone-pointer-arithmetic-on-polymorphic-object %t -- | ||
|
|
||
| class Base { | ||
| public: | ||
| virtual ~Base() {} | ||
| }; | ||
|
|
||
| class Derived : public Base {}; | ||
|
|
||
| class FinalDerived final : public Base {}; | ||
|
|
||
| class AbstractBase { | ||
| public: | ||
| virtual void f() = 0; | ||
| virtual ~AbstractBase() {} | ||
| }; | ||
|
|
||
| class AbstractInherited : public AbstractBase {}; | ||
|
|
||
| class AbstractOverride : public AbstractInherited { | ||
| public: | ||
| void f() override {} | ||
| }; | ||
|
|
||
| void operators() { | ||
| Base *b = new Derived[10]; | ||
|
|
||
| b += 1; | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on polymorphic object of type 'Base' can result in undefined behavior if the dynamic type differs from the pointer type [bugprone-pointer-arithmetic-on-polymorphic-object] | ||
|
|
||
| b = b + 1; | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: pointer arithmetic on polymorphic object of type 'Base' can result in undefined behavior if the dynamic type differs from the pointer type [bugprone-pointer-arithmetic-on-polymorphic-object] | ||
|
|
||
| b++; | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on polymorphic object of type 'Base' can result in undefined behavior if the dynamic type differs from the pointer type [bugprone-pointer-arithmetic-on-polymorphic-object] | ||
|
|
||
| --b; | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: pointer arithmetic on polymorphic object of type 'Base' can result in undefined behavior if the dynamic type differs from the pointer type [bugprone-pointer-arithmetic-on-polymorphic-object] | ||
|
|
||
| b[1]; | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on polymorphic object of type 'Base' can result in undefined behavior if the dynamic type differs from the pointer type [bugprone-pointer-arithmetic-on-polymorphic-object] | ||
|
|
||
| delete[] static_cast<Derived*>(b); | ||
| } | ||
|
|
||
| void subclassWarnings() { | ||
| Base *b = new Base[10]; | ||
|
|
||
| // False positive that's impossible to distinguish without | ||
| // path-sensitive analysis, but the code is bug-prone regardless. | ||
| b += 1; | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on polymorphic object of type 'Base' | ||
|
|
||
| delete[] b; | ||
|
|
||
| // Common false positive is a class that overrides all parent functions. | ||
| // Is a warning because of the check configuration. | ||
| Derived *d = new Derived[10]; | ||
|
|
||
| d += 1; | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on polymorphic object of type 'Derived' | ||
|
|
||
| delete[] d; | ||
|
|
||
| // Final classes cannot have a dynamic type. | ||
| FinalDerived *fd = new FinalDerived[10]; | ||
|
|
||
| fd += 1; | ||
| // no-warning | ||
|
|
||
| delete[] fd; | ||
| } | ||
|
|
||
| void abstractWarnings() { | ||
| // Classes with an abstract member funtion are always matched. | ||
| AbstractBase *ab = new AbstractOverride[10]; | ||
|
|
||
| ab += 1; | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on polymorphic object of type 'AbstractBase' | ||
|
|
||
| delete[] static_cast<AbstractOverride*>(ab); | ||
|
|
||
| AbstractInherited *ai = new AbstractOverride[10]; | ||
|
|
||
| ai += 1; | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on polymorphic object of type 'AbstractInherited' | ||
|
|
||
| delete[] static_cast<AbstractOverride*>(ai); | ||
|
|
||
| // Is a warning because of the check configuration. | ||
| AbstractOverride *ao = new AbstractOverride[10]; | ||
|
|
||
| ao += 1; | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on polymorphic object of type 'AbstractOverride' | ||
|
|
||
| delete[] ao; | ||
| } | ||
|
|
||
| template <typename T> | ||
| void templateWarning(T *t) { | ||
| // FIXME: Tidy doesn't support template instantiation locations properly. | ||
| t += 1; | ||
| // no-warning | ||
| } | ||
|
|
||
| void functionArgument(Base *b) { | ||
| b += 1; | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on polymorphic object of type 'Base' | ||
|
|
||
| templateWarning(b); | ||
| } | ||
|
|
||
| using BaseAlias = Base; | ||
| using DerivedAlias = Derived; | ||
| using FinalDerivedAlias = FinalDerived; | ||
|
|
||
| using BasePtr = Base*; | ||
| using DerivedPtr = Derived*; | ||
| using FinalDerivedPtr = FinalDerived*; | ||
|
|
||
| void typeAliases(BaseAlias *b, DerivedAlias *d, FinalDerivedAlias *fd, | ||
| BasePtr bp, DerivedPtr dp, FinalDerivedPtr fdp) { | ||
| b += 1; | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on polymorphic object of type 'Base' | ||
|
|
||
| d += 1; | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on polymorphic object of type 'Derived' | ||
|
|
||
| fd += 1; | ||
| // no-warning | ||
|
|
||
| bp += 1; | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on polymorphic object of type 'Base' | ||
|
|
||
| dp += 1; | ||
| // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: pointer arithmetic on polymorphic object of type 'Derived' | ||
|
|
||
| fdp += 1; | ||
| // no-warning | ||
| } |