183 changes: 97 additions & 86 deletions bolt/lib/Profile/YAMLProfileReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,13 @@ Error YAMLProfileReader::preprocessProfile(BinaryContext &BC) {
return Error::success();
}

bool YAMLProfileReader::profileMatches(
const yaml::bolt::BinaryFunctionProfile &Profile, const BinaryFunction &BF) {
if (opts::IgnoreHash)
return Profile.NumBasicBlocks == BF.size();
return Profile.Hash == static_cast<uint64_t>(BF.getHash());
}

bool YAMLProfileReader::mayHaveProfileData(const BinaryFunction &BF) {
if (opts::MatchProfileWithFunctionHash)
return true;
Expand All @@ -358,8 +365,92 @@ bool YAMLProfileReader::mayHaveProfileData(const BinaryFunction &BF) {
return false;
}

uint64_t YAMLProfileReader::matchWithNameSimilarity(BinaryContext &BC) {
uint64_t MatchedWithNameSimilarity = 0;
size_t YAMLProfileReader::matchWithExactName() {
size_t MatchedWithExactName = 0;
// This first pass assigns profiles that match 100% by name and by hash.
for (auto [YamlBF, BF] : llvm::zip_equal(YamlBP.Functions, ProfileBFs)) {
if (!BF)
continue;
BinaryFunction &Function = *BF;
// Clear function call count that may have been set while pre-processing
// the profile.
Function.setExecutionCount(BinaryFunction::COUNT_NO_PROFILE);

if (profileMatches(YamlBF, Function)) {
matchProfileToFunction(YamlBF, Function);
++MatchedWithExactName;
}
}
return MatchedWithExactName;
}

size_t YAMLProfileReader::matchWithHash(BinaryContext &BC) {
// Iterates through profiled functions to match the first binary function with
// the same exact hash. Serves to match identical, renamed functions.
// Collisions are possible where multiple functions share the same exact hash.
size_t MatchedWithHash = 0;
if (opts::MatchProfileWithFunctionHash) {
DenseMap<size_t, BinaryFunction *> StrictHashToBF;
StrictHashToBF.reserve(BC.getBinaryFunctions().size());

for (auto &[_, BF] : BC.getBinaryFunctions())
StrictHashToBF[BF.getHash()] = &BF;

for (yaml::bolt::BinaryFunctionProfile &YamlBF : YamlBP.Functions) {
if (YamlBF.Used)
continue;
auto It = StrictHashToBF.find(YamlBF.Hash);
if (It != StrictHashToBF.end() && !ProfiledFunctions.count(It->second)) {
BinaryFunction *BF = It->second;
matchProfileToFunction(YamlBF, *BF);
++MatchedWithHash;
}
}
}
return MatchedWithHash;
}

size_t YAMLProfileReader::matchWithLTOCommonName() {
// This second pass allows name ambiguity for LTO private functions.
size_t MatchedWithLTOCommonName = 0;
for (const auto &[CommonName, LTOProfiles] : LTOCommonNameMap) {
if (!LTOCommonNameFunctionMap.contains(CommonName))
continue;
std::unordered_set<BinaryFunction *> &Functions =
LTOCommonNameFunctionMap[CommonName];
// Return true if a given profile is matched to one of BinaryFunctions with
// matching LTO common name.
auto matchProfile = [&](yaml::bolt::BinaryFunctionProfile *YamlBF) {
if (YamlBF->Used)
return false;
for (BinaryFunction *BF : Functions) {
if (!ProfiledFunctions.count(BF) && profileMatches(*YamlBF, *BF)) {
matchProfileToFunction(*YamlBF, *BF);
++MatchedWithLTOCommonName;
return true;
}
}
return false;
};
bool ProfileMatched = llvm::any_of(LTOProfiles, matchProfile);

// If there's only one function with a given name, try to match it
// partially.
if (!ProfileMatched && LTOProfiles.size() == 1 && Functions.size() == 1 &&
!LTOProfiles.front()->Used &&
!ProfiledFunctions.count(*Functions.begin())) {
matchProfileToFunction(*LTOProfiles.front(), **Functions.begin());
++MatchedWithLTOCommonName;
}
}
return MatchedWithLTOCommonName;
}

size_t YAMLProfileReader::matchWithNameSimilarity(BinaryContext &BC) {
if (opts::NameSimilarityFunctionMatchingThreshold == 0)
return 0;

size_t MatchedWithNameSimilarity = 0;
ItaniumPartialDemangler Demangler;

// Demangle and derive namespace from function name.
Expand Down Expand Up @@ -477,17 +568,6 @@ Error YAMLProfileReader::readProfile(BinaryContext &BC) {
}
YamlProfileToFunction.resize(YamlBP.Functions.size() + 1);

auto profileMatches = [](const yaml::bolt::BinaryFunctionProfile &Profile,
BinaryFunction &BF) {
if (opts::IgnoreHash)
return Profile.NumBasicBlocks == BF.size();
return Profile.Hash == static_cast<uint64_t>(BF.getHash());
};

uint64_t MatchedWithExactName = 0;
uint64_t MatchedWithHash = 0;
uint64_t MatchedWithLTOCommonName = 0;

// Computes hash for binary functions.
if (opts::MatchProfileWithFunctionHash) {
for (auto &[_, BF] : BC.getBinaryFunctions()) {
Expand All @@ -501,84 +581,15 @@ Error YAMLProfileReader::readProfile(BinaryContext &BC) {
}
}

// This first pass assigns profiles that match 100% by name and by hash.
for (auto [YamlBF, BF] : llvm::zip_equal(YamlBP.Functions, ProfileBFs)) {
if (!BF)
continue;
BinaryFunction &Function = *BF;
// Clear function call count that may have been set while pre-processing
// the profile.
Function.setExecutionCount(BinaryFunction::COUNT_NO_PROFILE);

if (profileMatches(YamlBF, Function)) {
matchProfileToFunction(YamlBF, Function);
++MatchedWithExactName;
}
}

// Iterates through profiled functions to match the first binary function with
// the same exact hash. Serves to match identical, renamed functions.
// Collisions are possible where multiple functions share the same exact hash.
if (opts::MatchProfileWithFunctionHash) {
DenseMap<size_t, BinaryFunction *> StrictHashToBF;
StrictHashToBF.reserve(BC.getBinaryFunctions().size());

for (auto &[_, BF] : BC.getBinaryFunctions())
StrictHashToBF[BF.getHash()] = &BF;

for (yaml::bolt::BinaryFunctionProfile &YamlBF : YamlBP.Functions) {
if (YamlBF.Used)
continue;
auto It = StrictHashToBF.find(YamlBF.Hash);
if (It != StrictHashToBF.end() && !ProfiledFunctions.count(It->second)) {
BinaryFunction *BF = It->second;
matchProfileToFunction(YamlBF, *BF);
++MatchedWithHash;
}
}
}

// This second pass allows name ambiguity for LTO private functions.
for (const auto &[CommonName, LTOProfiles] : LTOCommonNameMap) {
if (!LTOCommonNameFunctionMap.contains(CommonName))
continue;
std::unordered_set<BinaryFunction *> &Functions =
LTOCommonNameFunctionMap[CommonName];
// Return true if a given profile is matched to one of BinaryFunctions with
// matching LTO common name.
auto matchProfile = [&](yaml::bolt::BinaryFunctionProfile *YamlBF) {
if (YamlBF->Used)
return false;
for (BinaryFunction *BF : Functions) {
if (!ProfiledFunctions.count(BF) && profileMatches(*YamlBF, *BF)) {
matchProfileToFunction(*YamlBF, *BF);
++MatchedWithLTOCommonName;
return true;
}
}
return false;
};
bool ProfileMatched = llvm::any_of(LTOProfiles, matchProfile);

// If there's only one function with a given name, try to match it
// partially.
if (!ProfileMatched && LTOProfiles.size() == 1 && Functions.size() == 1 &&
!LTOProfiles.front()->Used &&
!ProfiledFunctions.count(*Functions.begin())) {
matchProfileToFunction(*LTOProfiles.front(), **Functions.begin());
++MatchedWithLTOCommonName;
}
}
const size_t MatchedWithExactName = matchWithExactName();
const size_t MatchedWithHash = matchWithHash(BC);
const size_t MatchedWithLTOCommonName = matchWithLTOCommonName();
const size_t MatchedWithNameSimilarity = matchWithNameSimilarity(BC);

for (auto [YamlBF, BF] : llvm::zip_equal(YamlBP.Functions, ProfileBFs))
if (!YamlBF.Used && BF && !ProfiledFunctions.count(BF))
matchProfileToFunction(YamlBF, *BF);

// Uses name similarity to match functions that were not matched by name.
uint64_t MatchedWithNameSimilarity =
opts::NameSimilarityFunctionMatchingThreshold > 0
? matchWithNameSimilarity(BC)
: 0;

for (yaml::bolt::BinaryFunctionProfile &YamlBF : YamlBP.Functions)
if (!YamlBF.Used && opts::Verbosity >= 1)
Expand Down
14 changes: 5 additions & 9 deletions bolt/lib/Rewrite/DWARFRewriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1342,10 +1342,7 @@ void DWARFRewriter::updateDWARFObjectAddressRanges(
assert(RangesWriterIterator != LegacyRangesWritersByCU.end() &&
"RangesWriter does not exist for DWOId");
RangesWriterIterator->second->setDie(&Die);
} else if (Unit.getVersion() == 5) {
DIEBldr.addValue(&Die, dwarf::DW_AT_rnglists_base,
dwarf::DW_FORM_sec_offset, DIEInteger(*RangesBase));
} else {
} else if (Unit.getVersion() >= 5) {
DIEBldr.addValue(&Die, dwarf::DW_AT_rnglists_base,
dwarf::DW_FORM_sec_offset, DIEInteger(*RangesBase));
}
Expand Down Expand Up @@ -1638,14 +1635,13 @@ void DWARFRewriter::finalizeCompileUnits(DIEBuilder &DIEBlder,
"RangesWriter does not exist for DWOId");
std::unique_ptr<DebugRangesSectionWriter> &LegacyRangesWriter =
RangesWriterIterator->second;
std::optional<DIE *> Die = LegacyRangesWriter->getDie();
if (!Die || !Die.value())
DIE *Die = LegacyRangesWriter->getDie();
if (!Die)
continue;
DIEValue DvalGNUBase =
Die.value()->findAttribute(dwarf::DW_AT_GNU_ranges_base);
DIEValue DvalGNUBase = Die->findAttribute(dwarf::DW_AT_GNU_ranges_base);
assert(DvalGNUBase && "GNU_ranges_base attribute does not exist for DWOId");
DIEBlder.replaceValue(
Die.value(), dwarf::DW_AT_GNU_ranges_base, DvalGNUBase.getForm(),
Die, dwarf::DW_AT_GNU_ranges_base, DvalGNUBase.getForm(),
DIEInteger(LegacyRangesSectionWriter->getSectionOffset()));
std::unique_ptr<DebugBufferVector> RangesWritersContents =
LegacyRangesWriter->releaseBuffer();
Expand Down
29 changes: 27 additions & 2 deletions bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -702,8 +702,20 @@ class AArch64MCPlusBuilder : public MCPlusBuilder {
unsigned ShiftVal = AArch64_AM::getArithShiftValue(OperandExtension);
AArch64_AM::ShiftExtendType ExtendType =
AArch64_AM::getArithExtendType(OperandExtension);
if (ShiftVal != 2)
llvm_unreachable("Failed to match indirect branch! (fragment 2)");
if (ShiftVal != 2) {
// TODO: Handle the patten where ShiftVal != 2.
// The following code sequence below has no shift amount,
// the range could be 0 to 4.
// The pattern comes from libc, it occurs when the binary is static.
// 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
errs() << "BOLT-WARNING: "
"Failed to match indirect branch: ShiftVAL != 2 \n";
return false;
}

if (ExtendType == AArch64_AM::SXTB)
ScaleValue = 1LL;
Expand Down Expand Up @@ -748,6 +760,19 @@ class AArch64MCPlusBuilder : public MCPlusBuilder {
return true;
}

if (DefJTBaseAdd->getOpcode() == AArch64::ADR) {
// TODO: Handle the pattern where there is no adrp/add pair.
// It also occurs when the binary is static.
// 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
errs() << "BOLT-WARNING: Failed to match indirect branch: "
"nop/adr instead of adrp/add \n";
return false;
}

assert(DefJTBaseAdd->getOpcode() == AArch64::ADDXri &&
"Failed to match jump table base address pattern! (1)");

Expand Down
83 changes: 83 additions & 0 deletions bolt/test/AArch64/test-indirect-branch.s
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
11 changes: 5 additions & 6 deletions bolt/test/X86/dwarf5-df-types-debug-names.test
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,19 @@
; BOLT: type_signature = [[TYPE1:0x[0-9a-f]*]]
; BOLT: Compile Unit
; BOLT: type_signature = [[TYPE2:0x[0-9a-f]*]]
; BOLT: type_signature = [[TYPE3:0x[0-9a-f]*]]
; BOLT: type_signature = [[TYPE1]]
; BOLT: Compile Unit
; BOLT: [[OFFSET:0x[0-9a-f]*]]: Compile Unit
; BOLT: [[OFFSET1:0x[0-9a-f]*]]: Compile Unit

; BOLT: Name Index @ 0x0 {
; BOLT-NEXT: Header {
; BOLT-NEXT: Length: 0x17E
; BOLT-NEXT: Length: 0x176
; BOLT-NEXT: Format: DWARF32
; BOLT-NEXT: Version: 5
; BOLT-NEXT: CU count: 2
; BOLT-NEXT: Local TU count: 0
; BOLT-NEXT: Foreign TU count: 4
; BOLT-NEXT: Foreign TU count: 3
; BOLT-NEXT: Bucket count: 9
; BOLT-NEXT: Name count: 9
; BOLT-NEXT: Abbreviations table size: 0x37
Expand All @@ -44,7 +44,6 @@
; BOLT-NEXT: ForeignTU[0]: [[TYPE]]
; BOLT-NEXT: ForeignTU[1]: [[TYPE1]]
; BOLT-NEXT: ForeignTU[2]: [[TYPE2]]
; BOLT-NEXT: ForeignTU[3]: [[TYPE3]]
; BOLT-NEXT: ]
; BOLT-NEXT: Abbreviations [
; BOLT-NEXT: Abbreviation [[ABBREV:0x[0-9a-f]*]] {
Expand Down Expand Up @@ -173,7 +172,7 @@
; BOLT-NEXT: Entry @ {{.+}} {
; BOLT-NEXT: Abbrev: [[ABBREV]]
; BOLT-NEXT: Tag: DW_TAG_structure_type
; BOLT-NEXT: DW_IDX_type_unit: 0x03
; BOLT-NEXT: DW_IDX_type_unit: 0x01
; BOLT-NEXT: DW_IDX_compile_unit: 0x01
; BOLT-NEXT: DW_IDX_die_offset: 0x00000021
; BOLT-NEXT: DW_IDX_parent: <parent not indexed>
Expand Down Expand Up @@ -237,7 +236,7 @@
; BOLT-NEXT: Entry @ {{.+}} {
; BOLT-NEXT: Abbrev: 0x5
; BOLT-NEXT: Tag: DW_TAG_base_type
; BOLT-NEXT: DW_IDX_type_unit: 0x03
; BOLT-NEXT: DW_IDX_type_unit: 0x01
; BOLT-NEXT: DW_IDX_compile_unit: 0x01
; BOLT-NEXT: DW_IDX_die_offset: 0x00000048
; BOLT-NEXT: DW_IDX_parent: <parent not indexed>
Expand Down
2 changes: 1 addition & 1 deletion bolt/test/X86/infer_no_exits.test
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@

# PREAGG: B X:0 #main# 1 0

# CHECK: BOLT-INFO: inferred profile for 1 (100.00% of profiled, 100.00% of stale) functions responsible for -nan% samples (0 out of 0)
# CHECK: BOLT-INFO: inferred profile for 1 (100.00% of profiled, 100.00% of stale) functions
4 changes: 2 additions & 2 deletions bolt/test/X86/jt-symbol-disambiguation-4.s
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@
# REQUIRES: system-linux

# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-unknown %s -o %t.o
# RUN: %clang -no-pie %t.o -o %t.exe -Wl,-q
# RUN: %clang %cflags -no-pie %t.o -o %t.exe -Wl,-q
# RUN: llvm-bolt --funcs=main,foo/1 %t.exe -o %t.exe.bolt --print-normalized \
# RUN: 2>&1 | FileCheck %s

.text
.globl main
.type main,@function
main:
# CHECK: Binary Function "main"
# CHECK: Binary Function "main
pushq %rbp
movq %rsp, %rbp
movq $-16, %rax
Expand Down
4 changes: 4 additions & 0 deletions bolt/test/X86/register-fragments-bolt-symbols.s
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@
# PREAGGWARM: B X:0 #chain.warm# 1 0
# RUN: perf2bolt %t.warm.bolt -p %t.preagg.warm --pa -o %t.warm.fdata -w %t.warm.yaml \
# RUN: -v=1 | FileCheck %s --check-prefix=CHECK-BOLT-WARM
# RUN: FileCheck %s --input-file %t.warm.fdata --check-prefix=CHECK-FDATA-WARM
# RUN: FileCheck %s --input-file %t.warm.yaml --check-prefix=CHECK-YAML-WARM

# CHECK-BOLT-WARM: marking chain.warm/1(*2) as a fragment of chain
# CHECK-FDATA-WARM: chain
# CHECK-YAML-WARM: chain

# RUN: sed -i 's|chain|chain/2|g' %t.fdata
# RUN: llvm-objcopy --localize-symbol=chain %t.main.o
Expand Down
42 changes: 21 additions & 21 deletions clang-tools-extra/clang-doc/HTMLGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class HTMLTag {
operator bool() = delete;

bool isSelfClosing() const;
llvm::SmallString<16> ToString() const;
StringRef toString() const;

private:
TagType Value;
Expand Down Expand Up @@ -137,42 +137,42 @@ bool HTMLTag::isSelfClosing() const {
llvm_unreachable("Unhandled HTMLTag::TagType");
}

llvm::SmallString<16> HTMLTag::ToString() const {
StringRef HTMLTag::toString() const {
switch (Value) {
case HTMLTag::TAG_A:
return llvm::SmallString<16>("a");
return "a";
case HTMLTag::TAG_DIV:
return llvm::SmallString<16>("div");
return "div";
case HTMLTag::TAG_FOOTER:
return llvm::SmallString<16>("footer");
return "footer";
case HTMLTag::TAG_H1:
return llvm::SmallString<16>("h1");
return "h1";
case HTMLTag::TAG_H2:
return llvm::SmallString<16>("h2");
return "h2";
case HTMLTag::TAG_H3:
return llvm::SmallString<16>("h3");
return "h3";
case HTMLTag::TAG_HEADER:
return llvm::SmallString<16>("header");
return "header";
case HTMLTag::TAG_LI:
return llvm::SmallString<16>("li");
return "li";
case HTMLTag::TAG_LINK:
return llvm::SmallString<16>("link");
return "link";
case HTMLTag::TAG_MAIN:
return llvm::SmallString<16>("main");
return "main";
case HTMLTag::TAG_META:
return llvm::SmallString<16>("meta");
return "meta";
case HTMLTag::TAG_OL:
return llvm::SmallString<16>("ol");
return "ol";
case HTMLTag::TAG_P:
return llvm::SmallString<16>("p");
return "p";
case HTMLTag::TAG_SCRIPT:
return llvm::SmallString<16>("script");
return "script";
case HTMLTag::TAG_SPAN:
return llvm::SmallString<16>("span");
return "span";
case HTMLTag::TAG_TITLE:
return llvm::SmallString<16>("title");
return "title";
case HTMLTag::TAG_UL:
return llvm::SmallString<16>("ul");
return "ul";
}
llvm_unreachable("Unhandled HTMLTag::TagType");
}
Expand All @@ -191,7 +191,7 @@ void TagNode::render(llvm::raw_ostream &OS, int IndentationLevel) {
break;
}
OS.indent(IndentationLevel * 2);
OS << "<" << Tag.ToString();
OS << "<" << Tag.toString();
for (const auto &A : Attributes)
OS << " " << A.first << "=\"" << A.second << "\"";
if (Tag.isSelfClosing()) {
Expand All @@ -216,7 +216,7 @@ void TagNode::render(llvm::raw_ostream &OS, int IndentationLevel) {
}
if (!InlineChildren)
OS.indent(IndentationLevel * 2);
OS << "</" << Tag.ToString() << ">";
OS << "</" << Tag.toString() << ">";
}

template <typename Derived, typename Base,
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-doc/tool/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ set(assets
)

set(asset_dir "${CMAKE_CURRENT_SOURCE_DIR}/../assets")
set(resource_dir "${CMAKE_BINARY_DIR}/share/clang-doc")
set(resource_dir "${LLVM_RUNTIME_OUTPUT_INTDIR}/../share/clang-doc")
set(out_files)

function(copy_files_to_dst src_dir dst_dir file)
Expand Down
2 changes: 2 additions & 0 deletions clang-tools-extra/clang-tidy/boost/BoostTidyModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "../ClangTidy.h"
#include "../ClangTidyModule.h"
#include "../ClangTidyModuleRegistry.h"
#include "UseRangesCheck.h"
#include "UseToStringCheck.h"
using namespace clang::ast_matchers;

Expand All @@ -18,6 +19,7 @@ namespace boost {
class BoostModule : public ClangTidyModule {
public:
void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
CheckFactories.registerCheck<UseRangesCheck>("boost-use-ranges");
CheckFactories.registerCheck<UseToStringCheck>("boost-use-to-string");
}
};
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/boost/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ set(LLVM_LINK_COMPONENTS

add_clang_library(clangTidyBoostModule
BoostTidyModule.cpp
UseRangesCheck.cpp
UseToStringCheck.cpp

LINK_LIBS
Expand Down
371 changes: 371 additions & 0 deletions clang-tools-extra/clang-tidy/boost/UseRangesCheck.cpp
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
43 changes: 43 additions & 0 deletions clang-tools-extra/clang-tidy/boost/UseRangesCheck.h
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
3 changes: 3 additions & 0 deletions clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
#include "NotNullTerminatedResultCheck.h"
#include "OptionalValueConversionCheck.h"
#include "ParentVirtualCallCheck.h"
#include "PointerArithmeticOnPolymorphicObjectCheck.h"
#include "PosixReturnCheck.h"
#include "RedundantBranchConditionCheck.h"
#include "ReservedIdentifierCheck.h"
Expand Down Expand Up @@ -171,6 +172,8 @@ class BugproneModule : public ClangTidyModule {
"bugprone-multiple-statement-macro");
CheckFactories.registerCheck<OptionalValueConversionCheck>(
"bugprone-optional-value-conversion");
CheckFactories.registerCheck<PointerArithmeticOnPolymorphicObjectCheck>(
"bugprone-pointer-arithmetic-on-polymorphic-object");
CheckFactories.registerCheck<RedundantBranchConditionCheck>(
"bugprone-redundant-branch-condition");
CheckFactories.registerCheck<cppcoreguidelines::NarrowingConversionsCheck>(
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ add_clang_library(clangTidyBugproneModule
NotNullTerminatedResultCheck.cpp
OptionalValueConversionCheck.cpp
ParentVirtualCallCheck.cpp
PointerArithmeticOnPolymorphicObjectCheck.cpp
PosixReturnCheck.cpp
RedundantBranchConditionCheck.cpp
ReservedIdentifierCheck.cpp
Expand Down
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
42 changes: 33 additions & 9 deletions clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Analysis/Analyses/CFGReachabilityAnalysis.h"
#include "clang/Analysis/CFG.h"
#include "clang/Lex/Lexer.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallPtrSet.h"

#include "../utils/ExprSequence.h"
#include "../utils/Matchers.h"
Expand All @@ -34,7 +36,12 @@ struct UseAfterMove {
const DeclRefExpr *DeclRef;

// Is the order in which the move and the use are evaluated undefined?
bool EvaluationOrderUndefined;
bool EvaluationOrderUndefined = false;

// Does the use happen in a later loop iteration than the move?
//
// We default to false and change it to true if required in find().
bool UseHappensInLaterLoopIteration = false;
};

/// Finds uses of a variable after a move (and maintains state required by the
Expand All @@ -48,7 +55,7 @@ class UseAfterMoveFinder {
// use-after-move is found, writes information about it to 'TheUseAfterMove'.
// Returns whether a use-after-move was found.
bool find(Stmt *CodeBlock, const Expr *MovingCall,
const ValueDecl *MovedVariable, UseAfterMove *TheUseAfterMove);
const DeclRefExpr *MovedVariable, UseAfterMove *TheUseAfterMove);

private:
bool findInternal(const CFGBlock *Block, const Expr *MovingCall,
Expand Down Expand Up @@ -89,7 +96,7 @@ UseAfterMoveFinder::UseAfterMoveFinder(ASTContext *TheContext)
: Context(TheContext) {}

bool UseAfterMoveFinder::find(Stmt *CodeBlock, const Expr *MovingCall,
const ValueDecl *MovedVariable,
const DeclRefExpr *MovedVariable,
UseAfterMove *TheUseAfterMove) {
// Generate the CFG manually instead of through an AnalysisDeclContext because
// it seems the latter can't be used to generate a CFG for the body of a
Expand All @@ -110,15 +117,32 @@ bool UseAfterMoveFinder::find(Stmt *CodeBlock, const Expr *MovingCall,
BlockMap = std::make_unique<StmtToBlockMap>(TheCFG.get(), Context);
Visited.clear();

const CFGBlock *Block = BlockMap->blockContainingStmt(MovingCall);
if (!Block) {
const CFGBlock *MoveBlock = BlockMap->blockContainingStmt(MovingCall);
if (!MoveBlock) {
// This can happen if MovingCall is in a constructor initializer, which is
// not included in the CFG because the CFG is built only from the function
// body.
Block = &TheCFG->getEntry();
MoveBlock = &TheCFG->getEntry();
}

return findInternal(Block, MovingCall, MovedVariable, TheUseAfterMove);
bool Found = findInternal(MoveBlock, MovingCall, MovedVariable->getDecl(),
TheUseAfterMove);

if (Found) {
if (const CFGBlock *UseBlock =
BlockMap->blockContainingStmt(TheUseAfterMove->DeclRef)) {
// Does the use happen in a later loop iteration than the move?
// - If they are in the same CFG block, we know the use happened in a
// later iteration if we visited that block a second time.
// - Otherwise, we know the use happened in a later iteration if the
// move is reachable from the use.
CFGReverseBlockReachabilityAnalysis CFA(*TheCFG);
TheUseAfterMove->UseHappensInLaterLoopIteration =
UseBlock == MoveBlock ? Visited.contains(UseBlock)
: CFA.isReachable(UseBlock, MoveBlock);
}
}
return Found;
}

bool UseAfterMoveFinder::findInternal(const CFGBlock *Block,
Expand Down Expand Up @@ -394,7 +418,7 @@ static void emitDiagnostic(const Expr *MovingCall, const DeclRefExpr *MoveArg,
"there is no guarantee about the order in which they are evaluated",
DiagnosticIDs::Note)
<< IsMove;
} else if (UseLoc < MoveLoc || Use.DeclRef == MoveArg) {
} else if (Use.UseHappensInLaterLoopIteration) {
Check->diag(UseLoc,
"the use happens in a later loop iteration than the "
"%select{forward|move}0",
Expand Down Expand Up @@ -495,7 +519,7 @@ void UseAfterMoveCheck::check(const MatchFinder::MatchResult &Result) {
for (Stmt *CodeBlock : CodeBlocks) {
UseAfterMoveFinder Finder(Result.Context);
UseAfterMove Use;
if (Finder.find(CodeBlock, MovingCall, Arg->getDecl(), &Use))
if (Finder.find(CodeBlock, MovingCall, Arg, &Use))
emitDiagnostic(MovingCall, Arg, Use, this, Result.Context,
determineMoveType(MoveDecl));
}
Expand Down
5 changes: 5 additions & 0 deletions clang-tools-extra/clang-tidy/cert/CERTTidyModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "../ClangTidyModule.h"
#include "../ClangTidyModuleRegistry.h"
#include "../bugprone/BadSignalToKillThreadCheck.h"
#include "../bugprone/PointerArithmeticOnPolymorphicObjectCheck.h"
#include "../bugprone/ReservedIdentifierCheck.h"
#include "../bugprone/SignalHandlerCheck.h"
#include "../bugprone/SignedCharMisuseCheck.h"
Expand Down Expand Up @@ -238,6 +239,10 @@ class CERTModule : public ClangTidyModule {
// CON
CheckFactories.registerCheck<bugprone::SpuriouslyWakeUpFunctionsCheck>(
"cert-con54-cpp");
// CTR
CheckFactories
.registerCheck<bugprone::PointerArithmeticOnPolymorphicObjectCheck>(
"cert-ctr56-cpp");
// DCL
CheckFactories.registerCheck<VariadicFunctionDefCheck>("cert-dcl50-cpp");
CheckFactories.registerCheck<bugprone::ReservedIdentifierCheck>(
Expand Down
6 changes: 6 additions & 0 deletions clang-tools-extra/clang-tidy/misc/UseInternalLinkageCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ void UseInternalLinkageCheck::check(const MatchFinder::MatchResult &Result) {
return;
}
if (const auto *VD = Result.Nodes.getNodeAs<VarDecl>("var")) {
// In C++, const variables at file scope have implicit internal linkage,
// so we should not warn there. This is not the case in C.
// https://eel.is/c++draft/diff#basic-3
if (getLangOpts().CPlusPlus && VD->getType().isConstQualified())
return;

DiagnosticBuilder DB = diag(VD->getLocation(), Message) << "variable" << VD;
SourceLocation FixLoc = VD->getTypeSpecStartLoc();
if (FixLoc.isInvalid() || FixLoc.isMacroID())
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ add_clang_library(clangTidyModernizeModule
UseNoexceptCheck.cpp
UseNullptrCheck.cpp
UseOverrideCheck.cpp
UseRangesCheck.cpp
UseStartsEndsWithCheck.cpp
UseStdFormatCheck.cpp
UseStdNumbersCheck.cpp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include "UseNoexceptCheck.h"
#include "UseNullptrCheck.h"
#include "UseOverrideCheck.h"
#include "UseRangesCheck.h"
#include "UseStartsEndsWithCheck.h"
#include "UseStdFormatCheck.h"
#include "UseStdNumbersCheck.h"
Expand Down Expand Up @@ -75,6 +76,7 @@ class ModernizeModule : public ClangTidyModule {
CheckFactories.registerCheck<PassByValueCheck>("modernize-pass-by-value");
CheckFactories.registerCheck<UseDesignatedInitializersCheck>(
"modernize-use-designated-initializers");
CheckFactories.registerCheck<UseRangesCheck>("modernize-use-ranges");
CheckFactories.registerCheck<UseStartsEndsWithCheck>(
"modernize-use-starts-ends-with");
CheckFactories.registerCheck<UseStdFormatCheck>("modernize-use-std-format");
Expand Down
185 changes: 185 additions & 0 deletions clang-tools-extra/clang-tidy/modernize/UseRangesCheck.cpp
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
38 changes: 38 additions & 0 deletions clang-tools-extra/clang-tidy/modernize/UseRangesCheck.h
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
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/utils/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ add_clang_library(clangTidyUtils
RenamerClangTidyCheck.cpp
TransformerClangTidyCheck.cpp
TypeTraits.cpp
UseRangesCheck.cpp
UsingInserter.cpp

LINK_LIBS
Expand Down
68 changes: 62 additions & 6 deletions clang-tools-extra/clang-tidy/utils/ExprSequence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,18 @@ bool isDescendantOrEqual(const Stmt *Descendant, const Stmt *Ancestor,
ASTContext *Context) {
if (Descendant == Ancestor)
return true;
for (const Stmt *Parent : getParentStmts(Descendant, Context)) {
if (isDescendantOrEqual(Parent, Ancestor, Context))
return true;
}
return llvm::any_of(getParentStmts(Descendant, Context),
[Ancestor, Context](const Stmt *Parent) {
return isDescendantOrEqual(Parent, Ancestor, Context);
});
}

return false;
bool isDescendantOfArgs(const Stmt *Descendant, const CallExpr *Call,
ASTContext *Context) {
return llvm::any_of(Call->arguments(),
[Descendant, Context](const Expr *Arg) {
return isDescendantOrEqual(Descendant, Arg, Context);
});
}

llvm::SmallVector<const InitListExpr *>
Expand Down Expand Up @@ -95,9 +101,59 @@ bool ExprSequence::inSequence(const Stmt *Before, const Stmt *After) const {
return true;
}

SmallVector<const Stmt *, 1> BeforeParents = getParentStmts(Before, Context);

// Since C++17, the callee of a call expression is guaranteed to be sequenced
// before all of the arguments.
// We handle this as a special case rather than using the general
// `getSequenceSuccessor` logic above because the callee expression doesn't
// have an unambiguous successor; the order in which arguments are evaluated
// is indeterminate.
for (const Stmt *Parent : BeforeParents) {
// Special case: If the callee is a `MemberExpr` with a `DeclRefExpr` as its
// base, we consider it to be sequenced _after_ the arguments. This is
// because the variable referenced in the base will only actually be
// accessed when the call happens, i.e. once all of the arguments have been
// evaluated. This has no basis in the C++ standard, but it reflects actual
// behavior that is relevant to a use-after-move scenario:
//
// ```
// a.bar(consumeA(std::move(a));
// ```
//
// In this example, we end up accessing `a` after it has been moved from,
// even though nominally the callee `a.bar` is evaluated before the argument
// `consumeA(std::move(a))`. Note that this is not specific to C++17, so
// we implement this logic unconditionally.
if (const auto *Call = dyn_cast<CXXMemberCallExpr>(Parent)) {
if (is_contained(Call->arguments(), Before) &&
isa<DeclRefExpr>(
Call->getImplicitObjectArgument()->IgnoreParenImpCasts()) &&
isDescendantOrEqual(After, Call->getImplicitObjectArgument(),
Context))
return true;

// We need this additional early exit so that we don't fall through to the
// more general logic below.
if (const auto *Member = dyn_cast<MemberExpr>(Before);
Member && Call->getCallee() == Member &&
isa<DeclRefExpr>(Member->getBase()->IgnoreParenImpCasts()) &&
isDescendantOfArgs(After, Call, Context))
return false;
}

if (!Context->getLangOpts().CPlusPlus17)
continue;

if (const auto *Call = dyn_cast<CallExpr>(Parent);
Call && Call->getCallee() == Before &&
isDescendantOfArgs(After, Call, Context))
return true;
}

// If 'After' is a parent of 'Before' or is sequenced after one of these
// parents, we know that it is sequenced after 'Before'.
for (const Stmt *Parent : getParentStmts(Before, Context)) {
for (const Stmt *Parent : BeforeParents) {
if (Parent == After || inSequence(Parent, After))
return true;
}
Expand Down
306 changes: 306 additions & 0 deletions clang-tools-extra/clang-tidy/utils/UseRangesCheck.cpp
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
94 changes: 94 additions & 0 deletions clang-tools-extra/clang-tidy/utils/UseRangesCheck.h
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
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/support/ThreadsafeFS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class VolatileFileSystem : public llvm::vfs::ProxyFileSystem {
llvm::StringRef FileName = llvm::sys::path::filename(Path);
if (FileName.starts_with("preamble-") && FileName.ends_with(".pch"))
return File;
return std::unique_ptr<VolatileFile>(new VolatileFile(std::move(*File)));
return std::make_unique<VolatileFile>(std::move(*File));
}

private:
Expand Down
27 changes: 26 additions & 1 deletion clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,23 @@ Improvements to clang-tidy
New checks
^^^^^^^^^^

- New :doc:`boost-use-ranges
<clang-tidy/checks/boost/use-ranges>` check.

Detects calls to standard library iterator algorithms that could be replaced
with a Boost ranges version instead.

- New :doc:`bugprone-crtp-constructor-accessibility
<clang-tidy/checks/bugprone/crtp-constructor-accessibility>` check.

Detects error-prone Curiously Recurring Template Pattern usage, when the CRTP
can be constructed outside itself and the derived class.

- New :doc:`bugprone-pointer-arithmetic-on-polymorphic-object
<clang-tidy/checks/bugprone/pointer-arithmetic-on-polymorphic-object>` check.

Finds pointer arithmetic performed on classes that contain a virtual function.

- New :doc:`bugprone-return-const-ref-from-parameter
<clang-tidy/checks/bugprone/return-const-ref-from-parameter>` check.

Expand Down Expand Up @@ -169,6 +180,12 @@ New checks
Finds initializer lists for aggregate types that could be
written as designated initializers instead.

- New :doc:`modernize-use-ranges
<clang-tidy/checks/modernize/use-ranges>` check.

Detects calls to standard library iterator algorithms that could be replaced
with a ranges version instead.

- New :doc:`modernize-use-std-format
<clang-tidy/checks/modernize/use-std-format>` check.

Expand Down Expand Up @@ -199,6 +216,11 @@ New checks
New check aliases
^^^^^^^^^^^^^^^^^

- New alias :doc:`cert-ctr56-cpp <clang-tidy/checks/cert/ctr56-cpp>` to
:doc:`bugprone-pointer-arithmetic-on-polymorphic-object
<clang-tidy/checks/bugprone/pointer-arithmetic-on-polymorphic-object>`
was added.

- New alias :doc:`cert-int09-c <clang-tidy/checks/cert/int09-c>` to
:doc:`readability-enum-initial-value <clang-tidy/checks/readability/enum-initial-value>`
was added.
Expand Down Expand Up @@ -267,7 +289,10 @@ Changes in existing checks

- Improved :doc:`bugprone-use-after-move
<clang-tidy/checks/bugprone/use-after-move>` check to also handle
calls to ``std::forward``.
calls to ``std::forward``. Fixed sequencing of designated initializers. Fixed
sequencing of callees: In C++17 and later, the callee of a function is guaranteed
to be sequenced before the arguments, so don't warn if the use happens in the
callee and the move happens in one of the arguments.

- Improved :doc:`cppcoreguidelines-avoid-non-const-global-variables
<clang-tidy/checks/cppcoreguidelines/avoid-non-const-global-variables>` check
Expand Down
86 changes: 86 additions & 0 deletions clang-tools-extra/docs/clang-tidy/checks/boost/use-ranges.rst
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>`_.
10 changes: 10 additions & 0 deletions clang-tools-extra/docs/clang-tidy/checks/cert/ctr56-cpp.rst
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.
3 changes: 3 additions & 0 deletions clang-tools-extra/docs/clang-tidy/checks/list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ Clang-Tidy Checks
:doc:`android-cloexec-pipe2 <android/cloexec-pipe2>`, "Yes"
:doc:`android-cloexec-socket <android/cloexec-socket>`, "Yes"
:doc:`android-comparison-in-temp-failure-retry <android/comparison-in-temp-failure-retry>`,
:doc:`boost-use-ranges <boost/use-ranges>`, "Yes"
:doc:`boost-use-to-string <boost/use-to-string>`, "Yes"
:doc:`bugprone-argument-comment <bugprone/argument-comment>`, "Yes"
:doc:`bugprone-assert-side-effect <bugprone/assert-side-effect>`,
Expand Down Expand Up @@ -157,6 +158,7 @@ Clang-Tidy Checks
:doc:`bugprone-unused-raii <bugprone/unused-raii>`, "Yes"
:doc:`bugprone-unused-return-value <bugprone/unused-return-value>`,
:doc:`bugprone-use-after-move <bugprone/use-after-move>`,
:doc:`bugprone-pointer-arithmetic-on-polymorphic-object <bugprone/pointer-arithmetic-on-polymorphic-object>`,
:doc:`bugprone-virtual-near-miss <bugprone/virtual-near-miss>`, "Yes"
:doc:`cert-dcl50-cpp <cert/dcl50-cpp>`,
:doc:`cert-dcl58-cpp <cert/dcl58-cpp>`,
Expand Down Expand Up @@ -300,6 +302,7 @@ Clang-Tidy Checks
:doc:`modernize-use-noexcept <modernize/use-noexcept>`, "Yes"
:doc:`modernize-use-nullptr <modernize/use-nullptr>`, "Yes"
:doc:`modernize-use-override <modernize/use-override>`, "Yes"
:doc:`modernize-use-ranges <modernize/use-ranges>`, "Yes"
:doc:`modernize-use-starts-ends-with <modernize/use-starts-ends-with>`, "Yes"
:doc:`modernize-use-std-format <modernize/use-std-format>`, "Yes"
:doc:`modernize-use-std-numbers <modernize/use-std-numbers>`, "Yes"
Expand Down
79 changes: 79 additions & 0 deletions clang-tools-extra/docs/clang-tidy/checks/modernize/use-ranges.rst
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`.

194 changes: 194 additions & 0 deletions clang-tools-extra/test/clang-tidy/checkers/boost/use-ranges.cpp
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
}
Loading