49 changes: 23 additions & 26 deletions bolt/lib/Rewrite/DWARFRewriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -620,8 +620,10 @@ void DWARFRewriter::updateDebugInfo() {
AddrWriter = std::make_unique<DebugAddrWriter>(&BC);
}

if (BC.isDWARFLegacyUsed())
if (BC.isDWARFLegacyUsed()) {
LegacyRangesSectionWriter = std::make_unique<DebugRangesSectionWriter>();
LegacyRangesSectionWriter->initSection();
}

DebugLoclistWriter::setAddressWriter(AddrWriter.get());

Expand Down Expand Up @@ -651,7 +653,6 @@ void DWARFRewriter::updateDebugInfo() {
"LegacyRangeLists writer for DWO unit already exists.");
auto LegacyRangesSectionWriterByCU =
std::make_unique<DebugRangesSectionWriter>();
LegacyRangesSectionWriterByCU->initSection(CU);
LegacyRangesWritersByCU[*DWOId] =
std::move(LegacyRangesSectionWriterByCU);
}
Expand Down Expand Up @@ -1342,10 +1343,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 +1636,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 Expand Up @@ -2165,21 +2162,21 @@ void DWARFRewriter::convertToRangesPatchDebugInfo(
DIEBldr.replaceValue(&Die, LowPCAttrInfo.getAttribute(),
LowPCAttrInfo.getForm(), DIEInteger(0));
}
}
// Original CU didn't have DW_AT_*_base. We converted it's children (or
// dwo), so need to insert it into CU.
if (RangesBase) {
if (Unit.getVersion() >= 5) {
DIEBldr.addValue(&Die, RangeBaseAttribute, dwarf::DW_FORM_sec_offset,
DIEInteger(*RangesBase));
} else {
DIEBldr.addValue(&Die, RangeBaseAttribute, dwarf::DW_FORM_sec_offset,
DIEInteger(INT_MAX));
auto RangesWriterIterator =
LegacyRangesWritersByCU.find(*Unit.getDWOId());
assert(RangesWriterIterator != LegacyRangesWritersByCU.end() &&
"RangesWriter does not exist for DWOId");
RangesWriterIterator->second->setDie(&Die);
// Original CU didn't have DW_AT_*_base. We converted it's children (or
// dwo), so need to insert it into CU.
if (RangesBase) {
if (Unit.getVersion() >= 5) {
DIEBldr.addValue(&Die, RangeBaseAttribute, dwarf::DW_FORM_sec_offset,
DIEInteger(*RangesBase));
} else {
DIEBldr.addValue(&Die, RangeBaseAttribute, dwarf::DW_FORM_sec_offset,
DIEInteger(INT_MAX));
auto RangesWriterIterator =
LegacyRangesWritersByCU.find(*Unit.getDWOId());
assert(RangesWriterIterator != LegacyRangesWritersByCU.end() &&
"RangesWriter does not exist for DWOId");
RangesWriterIterator->second->setDie(&Die);
}
}
}

Expand Down
8 changes: 4 additions & 4 deletions bolt/test/X86/debug-fission-single-convert.s
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@

# CHECK-NOT: warning: DWARF unit from offset {{.*}} incl. to offset {{.*}} excl. tries to read DIEs at offset {{.*}}

# CHECK-DWO-DWO: 00000000
# CHECK-DWO-DWO: 00000010
# CHECK-DWO-DWO: 00000010
# CHECK-DWO-DWO: 00000050
# CHECK-DWO-DWO: DW_TAG_subprogram
# CHECK-DWO-DWO-NEXT: DW_AT_ranges [DW_FORM_sec_offset] (0x00000010
# CHECK-DWO-DWO-NEXT: DW_AT_ranges [DW_FORM_sec_offset] (0x00000000
# CHECK-DWO-DWO: DW_TAG_subprogram
# CHECK-DWO-DWO-NEXT: DW_AT_ranges [DW_FORM_sec_offset] (0x00000030
# CHECK-DWO-DWO-NEXT: DW_AT_ranges [DW_FORM_sec_offset] (0x00000020
# CHECK-DWO-DWO: DW_TAG_subprogram
# CHECK-DWO-DWO-NEXT: DW_AT_ranges [DW_FORM_sec_offset] (0x00000050
# CHECK-DWO-DWO-NEXT: DW_AT_ranges [DW_FORM_sec_offset] (0x00000040

# CHECK-ADDR-SEC: .debug_addr contents:
# CHECK-ADDR-SEC: 0x00000000: Addrs: [
Expand Down
26 changes: 12 additions & 14 deletions bolt/test/X86/dwarf4-df-dualcu.test
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,14 @@
; BOLT-NEXT: 00000010 [[#%.16x,ADDR1:]] [[#%.16x,ADDRB1:]]
; BOLT-NEXT: 00000010 [[#%.16x,ADDR2:]] [[#%.16x,ADDRB2:]]
; BOLT-NEXT: 00000010 <End of list>
; BOLT-NEXT: 00000040 [[#%.16x,ADDR1:]] [[#%.16x,ADDRB1:]]
; BOLT-NEXT: 00000040 <End of list>
; BOLT-NEXT: 00000050 [[#%.16x,ADDR1:]] [[#%.16x,ADDRB1:]]
; BOLT-NEXT: 00000050 <End of list>
; BOLT-NEXT: 00000070 [[#%.16x,ADDR2:]] [[#%.16x,ADDRB2:]]
; BOLT-NEXT: 00000070 <End of list>
; BOLT-NEXT: 00000090 [[#%.16x,ADDR3:]] [[#%.16x,ADDRB3:]]
; BOLT-NEXT: 00000090 <End of list>
; BOLT-NEXT: 000000b0 <End of list>
; BOLT-NEXT: 000000c0 [[#%.16x,ADDR3:]] [[#%.16x,ADDRB3:]]
; BOLT-NEXT: 000000c0 <End of list>
; BOLT-NEXT: 00000060 [[#%.16x,ADDR2:]] [[#%.16x,ADDRB2:]]
; BOLT-NEXT: 00000060 <End of list>
; BOLT-NEXT: 00000080 [[#%.16x,ADDR3:]] [[#%.16x,ADDRB3:]]
; BOLT-NEXT: 00000080 <End of list>
; BOLT-NEXT: 000000a0 [[#%.16x,ADDR3:]] [[#%.16x,ADDRB3:]]
; BOLT-NEXT: 000000a0 <End of list>

; BOLT: DW_TAG_compile_unit
; BOLT: DW_AT_GNU_dwo_name [DW_FORM_strp] ( .debug_str[0x00000016] = "main.dwo.dwo")
Expand All @@ -65,10 +63,10 @@
; BOLT: DW_AT_GNU_dwo_name [DW_FORM_strp] ( .debug_str[0x00000023] = "helper.dwo.dwo")
; BOLT-NEXT: DW_AT_GNU_dwo_id
; BOLT-NEXT: DW_AT_low_pc [DW_FORM_addr] (0x0000000000000000)
; BOLT-NEXT: DW_AT_ranges [DW_FORM_sec_offset] (0x00000090
; BOLT-NEXT: DW_AT_ranges [DW_FORM_sec_offset] (0x00000080
; BOLT-NEXT: [0x[[#ADDR3]], 0x[[#ADDRB3]])
; BOLT-NEXT: DW_AT_GNU_addr_base [DW_FORM_sec_offset] (0x00000010)
; BOLT-NEXT: DW_AT_GNU_ranges_base [DW_FORM_sec_offset] (0x000000b0)
; BOLT-NEXT: DW_AT_GNU_ranges_base [DW_FORM_sec_offset] (0x000000a0)

; PRE-BOLT-DWO-MAIN: version = 0x0004
; PRE-BOLT-DWO-MAIN: DW_TAG_compile_unit
Expand Down Expand Up @@ -115,13 +113,13 @@
; BOLT-DWO-MAIN-NEXT: DW_AT_decl_line
; BOLT-DWO-MAIN-NEXT: DW_AT_location [DW_FORM_exprloc] (DW_OP_GNU_addr_index 0x1)
; BOLT-DWO-MAIN: DW_TAG_subprogram [4]
; BOLT-DWO-MAIN-NEXT: DW_AT_ranges [DW_FORM_sec_offset] (0x00000010
; BOLT-DWO-MAIN-NEXT: DW_AT_ranges [DW_FORM_sec_offset] (0x00000000
; BOLT-DWO-MAIN-NEXT: )
; BOLT-DWO-MAIN-NEXT: DW_AT_frame_base
; BOLT-DWO-MAIN-NEXT: DW_AT_linkage_name [DW_FORM_GNU_str_index] (indexed (00000003) string = "_Z3usePiS_")
; BOLT-DWO-MAIN-NEXT: DW_AT_name [DW_FORM_GNU_str_index] (indexed (00000004) string = "use")
; BOLT-DWO-MAIN: DW_TAG_subprogram [6]
; BOLT-DWO-MAIN-NEXT: DW_AT_ranges [DW_FORM_sec_offset] (0x00000030
; BOLT-DWO-MAIN-NEXT: DW_AT_ranges [DW_FORM_sec_offset] (0x00000020
; BOLT-DWO-MAIN-NEXT: )
; BOLT-DWO-MAIN-NEXT: DW_AT_frame_base [DW_FORM_exprloc] (DW_OP_reg6 RBP)
; BOLT-DWO-MAIN-NEXT: DW_AT_name [DW_FORM_GNU_str_index] (indexed (00000005) string = "main")
Expand Down Expand Up @@ -162,4 +160,4 @@
; BOLT-DWO-HELPER-NEXT: DW_AT_decl_line
; BOLT-DWO-HELPER-NEXT: DW_AT_location [DW_FORM_exprloc] (DW_OP_GNU_addr_index 0x1)
; BOLT-DWO-HELPER: DW_TAG_subprogram [4]
; BOLT-DWO-HELPER-NEXT: DW_AT_ranges [DW_FORM_sec_offset] (0x00000010
; BOLT-DWO-HELPER-NEXT: DW_AT_ranges [DW_FORM_sec_offset] (0x00000000
60 changes: 29 additions & 31 deletions bolt/test/X86/dwarf4-df-input-lowpc-ranges-cus.test
Original file line number Diff line number Diff line change
Expand Up @@ -25,32 +25,30 @@
; BOLT-NEXT: 00000010 [[#%.16x,ADDR6:]] [[#%.16x,ADDRB6:]]
; BOLT-NEXT: 00000010 [[#%.16x,ADDR7:]] [[#%.16x,ADDRB7:]]
; BOLT-NEXT: 00000010 <End of list>
; BOLT-NEXT: 00000090 [[#%.16x,ADDR1:]] [[#%.16x,ADDRB1:]]
; BOLT-NEXT: 00000090 [[#%.16x,ADDR2:]] [[#%.16x,ADDRB2:]]
; BOLT-NEXT: 00000090 [[#%.16x,ADDR3:]] [[#%.16x,ADDRB3:]]
; BOLT-NEXT: 00000090 <End of list>
; BOLT-NEXT: 000000a0 [[#%.16x,ADDR1:]] [[#%.16x,ADDRB1:]]
; BOLT-NEXT: 000000a0 [[#%.16x,ADDR2:]] [[#%.16x,ADDRB2:]]
; BOLT-NEXT: 000000a0 [[#%.16x,ADDR3:]] [[#%.16x,ADDRB3:]]
; BOLT-NEXT: 000000a0 <End of list>
; BOLT-NEXT: 000000e0 [[#%.16x,ADDR5:]] [[#%.16x,ADDRB5:]]
; BOLT-NEXT: 000000e0 [[#%.16x,ADDR6:]] [[#%.16x,ADDRB6:]]
; BOLT-NEXT: 000000e0 [[#%.16x,ADDR7:]] [[#%.16x,ADDRB7:]]
; BOLT-NEXT: 000000e0 <End of list>
; BOLT-NEXT: 00000120 [[#%.16x,ADDR8:]] [[#%.16x,ADDRB8:]]
; BOLT-NEXT: 00000120 [[#%.16x,ADDR9:]] [[#%.16x,ADDRB9:]]
; BOLT-NEXT: 00000120 [[#%.16x,ADDR10:]] [[#%.16x,ADDRB10:]]
; BOLT-NEXT: 00000120 [[#%.16x,ADDR11:]] [[#%.16x,ADDRB11:]]
; BOLT-NEXT: 00000120 [[#%.16x,ADDR12:]] [[#%.16x,ADDRB12:]]
; BOLT-NEXT: 00000120 [[#%.16x,ADDR13:]] [[#%.16x,ADDRB13:]]
; BOLT-NEXT: 00000120 [[#%.16x,ADDR14:]] [[#%.16x,ADDRB14:]]
; BOLT-NEXT: 00000120 <End of list>
; BOLT-NEXT: 000001a0 <End of list>
; BOLT-NEXT: 000001b0 [[#%.16x,ADDR8:]] [[#%.16x,ADDRB8:]]
; BOLT-NEXT: 000001b0 [[#%.16x,ADDR9:]] [[#%.16x,ADDRB9:]]
; BOLT-NEXT: 000001b0 [[#%.16x,ADDR10:]] [[#%.16x,ADDRB10:]]
; BOLT-NEXT: 000001b0 <End of list>
; BOLT-NEXT: 000001f0 [[#%.16x,ADDR12:]] [[#%.16x,ADDRB12:]]
; BOLT-NEXT: 000001f0 [[#%.16x,ADDR13:]] [[#%.16x,ADDRB13:]]
; BOLT-NEXT: 000001f0 [[#%.16x,ADDR14:]] [[#%.16x,ADDRB14:]]
; BOLT-NEXT: 000001f0 <End of list>
; BOLT-NEXT: 000000d0 [[#%.16x,ADDR5:]] [[#%.16x,ADDRB5:]]
; BOLT-NEXT: 000000d0 [[#%.16x,ADDR6:]] [[#%.16x,ADDRB6:]]
; BOLT-NEXT: 000000d0 [[#%.16x,ADDR7:]] [[#%.16x,ADDRB7:]]
; BOLT-NEXT: 000000d0 <End of list>
; BOLT-NEXT: 00000110 [[#%.16x,ADDR8:]] [[#%.16x,ADDRB8:]]
; BOLT-NEXT: 00000110 [[#%.16x,ADDR9:]] [[#%.16x,ADDRB9:]]
; BOLT-NEXT: 00000110 [[#%.16x,ADDR10:]] [[#%.16x,ADDRB10:]]
; BOLT-NEXT: 00000110 [[#%.16x,ADDR11:]] [[#%.16x,ADDRB11:]]
; BOLT-NEXT: 00000110 [[#%.16x,ADDR12:]] [[#%.16x,ADDRB12:]]
; BOLT-NEXT: 00000110 [[#%.16x,ADDR13:]] [[#%.16x,ADDRB13:]]
; BOLT-NEXT: 00000110 [[#%.16x,ADDR14:]] [[#%.16x,ADDRB14:]]
; BOLT-NEXT: 00000110 <End of list>
; BOLT-NEXT: 00000190 [[#%.16x,ADDR8:]] [[#%.16x,ADDRB8:]]
; BOLT-NEXT: 00000190 [[#%.16x,ADDR9:]] [[#%.16x,ADDRB9:]]
; BOLT-NEXT: 00000190 [[#%.16x,ADDR10:]] [[#%.16x,ADDRB10:]]
; BOLT-NEXT: 00000190 <End of list>
; BOLT-NEXT: 000001d0 [[#%.16x,ADDR12:]] [[#%.16x,ADDRB12:]]
; BOLT-NEXT: 000001d0 [[#%.16x,ADDR13:]] [[#%.16x,ADDRB13:]]
; BOLT-NEXT: 000001d0 [[#%.16x,ADDR14:]] [[#%.16x,ADDRB14:]]
; BOLT-NEXT: 000001d0 <End of list>

; BOLT: DW_TAG_compile_unit
; BOLT: DW_AT_GNU_dwo_name [DW_FORM_strp] ( .debug_str[0x{{[0-9a-fA-F]+}}] = "main.dwo.dwo")
Expand All @@ -71,9 +69,9 @@
; BOLT: DW_TAG_compile_unit
; BOLT: DW_AT_GNU_dwo_name [DW_FORM_strp] ( .debug_str[0x{{[0-9a-fA-F]+}}] = "mainOther.dwo.dwo")
; BOLT-NEXT: DW_AT_GNU_dwo_id
; BOLT-NEXT: DW_AT_GNU_ranges_base [DW_FORM_sec_offset] (0x000001a0)
; BOLT-NEXT: DW_AT_GNU_ranges_base [DW_FORM_sec_offset] (0x00000190)
; BOLT-NEXT: DW_AT_low_pc [DW_FORM_addr] (0x0000000000000000)
; BOLT-NEXT: DW_AT_ranges [DW_FORM_sec_offset] (0x00000120
; BOLT-NEXT: DW_AT_ranges [DW_FORM_sec_offset] (0x00000110
; BOLT-NEXT: [0x[[#ADDR8]], 0x[[#ADDRB8]])
; BOLT-NEXT: [0x[[#ADDR9]], 0x[[#ADDRB9]])
; BOLT-NEXT: [0x[[#ADDR10]], 0x[[#ADDRB10]])
Expand All @@ -85,17 +83,17 @@
; BOLT: {{^$}}

; BOLT-DWO-MAIN: DW_TAG_subprogram
; BOLT-DWO-MAIN-NEXT: DW_AT_ranges [DW_FORM_sec_offset] (0x00000010
; BOLT-DWO-MAIN-NEXT: DW_AT_ranges [DW_FORM_sec_offset] (0x00000000
; BOLT-DWO-MAIN: DW_TAG_subprogram
; BOLT-DWO-MAIN: DW_TAG_subprogram
; BOLT-DWO-MAIN: DW_TAG_subprogram
; BOLT-DWO-MAIN: DW_TAG_subprogram
; BOLT-DWO-MAIN-NEXT: DW_AT_ranges [DW_FORM_sec_offset] (0x00000050
; BOLT-DWO-MAIN-NEXT: DW_AT_ranges [DW_FORM_sec_offset] (0x00000040

; BOLT-DWO-MAIN: DW_TAG_subprogram
; BOLT-DWO-MAIN-NEXT: DW_AT_ranges [DW_FORM_sec_offset] (0x00000010
; BOLT-DWO-MAIN-NEXT: DW_AT_ranges [DW_FORM_sec_offset] (0x00000000
; BOLT-DWO-MAIN: DW_TAG_subprogram
; BOLT-DWO-MAIN: DW_TAG_subprogram
; BOLT-DWO-MAIN: DW_TAG_subprogram
; BOLT-DWO-MAIN: DW_TAG_subprogram
; BOLT-DWO-MAIN-NEXT: DW_AT_ranges [DW_FORM_sec_offset] (0x00000050
; BOLT-DWO-MAIN-NEXT: DW_AT_ranges [DW_FORM_sec_offset] (0x00000040
19 changes: 9 additions & 10 deletions bolt/test/X86/dwarf4-df-input-lowpc-ranges.test
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,14 @@
; BOLT-NEXT: 00000010 [[#%.16x,ADDR6:]] [[#%.16x,ADDRB6:]]
; BOLT-NEXT: 00000010 [[#%.16x,ADDR7:]] [[#%.16x,ADDRB7:]]
; BOLT-NEXT: 00000010 <End of list>
; BOLT-NEXT: 00000090 [[#%.16x,ADDR1:]] [[#%.16x,ADDRB1:]]
; BOLT-NEXT: 00000090 [[#%.16x,ADDR2:]] [[#%.16x,ADDRB2:]]
; BOLT-NEXT: 00000090 [[#%.16x,ADDR3:]] [[#%.16x,ADDRB3:]]
; BOLT-NEXT: 00000090 <End of list>
; BOLT-NEXT: 000000a0 [[#%.16x,ADDR1:]] [[#%.16x,ADDRB1:]]
; BOLT-NEXT: 000000a0 [[#%.16x,ADDR2:]] [[#%.16x,ADDRB2:]]
; BOLT-NEXT: 000000a0 [[#%.16x,ADDR3:]] [[#%.16x,ADDRB3:]]
; BOLT-NEXT: 000000a0 <End of list>
; BOLT-NEXT: 000000e0 [[#%.16x,ADDR5:]] [[#%.16x,ADDRB5:]]
; BOLT-NEXT: 000000e0 [[#%.16x,ADDR6:]] [[#%.16x,ADDRB6:]]
; BOLT-NEXT: 000000e0 [[#%.16x,ADDR7:]] [[#%.16x,ADDRB7:]]
; BOLT-NEXT: 000000e0 <End of list>
; BOLT-NEXT: 000000d0 [[#%.16x,ADDR5:]] [[#%.16x,ADDRB5:]]
; BOLT-NEXT: 000000d0 [[#%.16x,ADDR6:]] [[#%.16x,ADDRB6:]]
; BOLT-NEXT: 000000d0 [[#%.16x,ADDR7:]] [[#%.16x,ADDRB7:]]
; BOLT-NEXT: 000000d0 <End of list>

; BOLT: DW_TAG_compile_unit
; BOLT: DW_AT_GNU_dwo_name [DW_FORM_strp] ( .debug_str[0x{{[0-9a-fA-F]+}}] = "main.dwo.dwo")
Expand All @@ -49,9 +48,9 @@
; BOLT-NEXT: DW_AT_GNU_addr_base [DW_FORM_sec_offset] (0x00000000)

; BOLT-DWO-MAIN: DW_TAG_subprogram
; BOLT-DWO-MAIN-NEXT: DW_AT_ranges [DW_FORM_sec_offset] (0x00000010
; BOLT-DWO-MAIN-NEXT: DW_AT_ranges [DW_FORM_sec_offset] (0x00000000
; BOLT-DWO-MAIN: DW_TAG_subprogram
; BOLT-DWO-MAIN: DW_TAG_subprogram
; BOLT-DWO-MAIN: DW_TAG_subprogram
; BOLT-DWO-MAIN: DW_TAG_subprogram
; BOLT-DWO-MAIN-NEXT: DW_AT_ranges [DW_FORM_sec_offset] (0x00000050
; BOLT-DWO-MAIN-NEXT: DW_AT_ranges [DW_FORM_sec_offset] (0x00000040
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
162 changes: 162 additions & 0 deletions bolt/test/X86/match-functions-with-calls-as-anchors.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
## Tests blocks matching by called function names in inferStaleProfile.

# REQUIRES: system-linux
# RUN: split-file %s %t
# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-unknown %t/main.s -o %t.o
# RUN: %clang %cflags %t.o -o %t.exe -Wl,-q -nostdlib
# RUN: llvm-bolt %t.exe -o %t.out --data %t/yaml --profile-ignore-hash -v=1 \
# RUN: --dyno-stats --print-cfg --infer-stale-profile=1 --debug 2>&1 | FileCheck %s

# CHECK: BOLT-INFO: applying profile inference for "qux"
# CHECK: Matched yaml block (bid = 1) with hash 4 to BB (index = 0) with hash 314e1bc10000
# CHECK: loose match

# CHECK: BOLT-INFO: applying profile inference for "fred"
# CHECK: Matched yaml block (bid = 1) with hash 5 to BB (index = 0) with hash 7541bc10000
# CHECK: loose match

#--- main.s
.globl foo # -- Begin function foo
.p2align 4, 0x90
.type foo,@function
foo: # @foo
# %bb.0:
pushq %rbp
movq %rsp, %rbp
popq %rbp
retq
.Lfunc_end0:
.size foo, .Lfunc_end0-foo
# -- End function
.globl bar # -- Begin function bar
.p2align 4, 0x90
.type bar,@function
bar: # @bar
# %bb.0:
pushq %rbp
movq %rsp, %rbp
popq %rbp
retq
.Lfunc_end1:
.size bar, .Lfunc_end1-bar
# -- End function
.globl qux # -- Begin function qux
.p2align 4, 0x90
.type qux,@function
qux: # @qux
# %bb.0:
pushq %rbp
movq %rsp, %rbp
callq foo
callq bar
popq %rbp
retq
.Lfunc_end2:
.size qux, .Lfunc_end2-qux
# -- End function
.globl fred # -- Begin function fred
.p2align 4, 0x90
.type fred,@function
fred: # @fred
# %bb.0:
pushq %rbp
movq %rsp, %rbp
callq foo
callq qux
callq bar
callq bar
callq foo
popq %rbp
retq
.Lfunc_end3:
.size fred, .Lfunc_end3-fred
# -- End function
.globl main # -- Begin function main
.p2align 4, 0x90
.type main,@function
main: # @main
# %bb.0:
pushq %rbp
movq %rsp, %rbp
xorl %eax, %eax
popq %rbp
retq
.Lfunc_end4:
.size main, .Lfunc_end4-main
# -- End function
.addrsig
.addrsig_sym foo
.addrsig_sym bar
.addrsig_sym qux

#--- yaml
---
header:
profile-version: 1
binary-name: 'match-functions-with-calls-as-anchors.s.tmp.exe'
binary-build-id: '<unknown>'
profile-flags: [ lbr ]
profile-origin: branch profile reader
profile-events: ''
dfs-order: false
hash-func: xxh3
functions:
- name: main
fid: 0
hash: 0x0000000000000001
exec: 1
nblocks: 6
blocks:
- bid: 1
hash: 0x0000000000000001
insns: 1
succ: [ { bid: 3, cnt: 1} ]
- name: foo
fid: 1
hash: 0x0000000000000002
exec: 1
nblocks: 6
blocks:
- bid: 1
hash: 0x0000000000000002
insns: 1
succ: [ { bid: 3, cnt: 1} ]

- name: bar
fid: 2
hash: 0x0000000000000003
exec: 1
nblocks: 6
blocks:
- bid: 1
hash: 0x0000000000000003
insns: 1
succ: [ { bid: 3, cnt: 1} ]
- name: qux
fid: 3
hash: 0x0000000000000004
exec: 4
nblocks: 6
blocks:
- bid: 1
hash: 0x0000000000000004
insns: 1
succ: [ { bid: 3, cnt: 1} ]
calls: [ { off : 0, fid : 1, cnt : 0},
{ off : 0, fid : 2, cnt : 0} ]
- name: fred
fid: 4
hash: 0x0000000000000005
exec: 1
nblocks: 6
blocks:
- bid: 1
hash: 0x0000000000000005
insns: 1
succ: [ { bid: 3, cnt: 1} ]
calls: [ { off : 0, fid : 3, cnt : 0},
{ off : 0, fid : 1, cnt : 0},
{ off : 0, fid : 2, cnt : 0},
{ off : 0, fid : 1, cnt : 0},
{ off : 0, fid : 2, cnt : 0} ]
...
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
84 changes: 55 additions & 29 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 @@ -47,13 +54,13 @@ class UseAfterMoveFinder {
// occurs after 'MovingCall' (the expression that performs the move). If a
// 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);
std::optional<UseAfterMove> find(Stmt *CodeBlock, const Expr *MovingCall,
const DeclRefExpr *MovedVariable);

private:
bool findInternal(const CFGBlock *Block, const Expr *MovingCall,
const ValueDecl *MovedVariable,
UseAfterMove *TheUseAfterMove);
std::optional<UseAfterMove> findInternal(const CFGBlock *Block,
const Expr *MovingCall,
const ValueDecl *MovedVariable);
void getUsesAndReinits(const CFGBlock *Block, const ValueDecl *MovedVariable,
llvm::SmallVectorImpl<const DeclRefExpr *> *Uses,
llvm::SmallPtrSetImpl<const Stmt *> *Reinits);
Expand Down Expand Up @@ -88,9 +95,9 @@ static StatementMatcher inDecltypeOrTemplateArg() {
UseAfterMoveFinder::UseAfterMoveFinder(ASTContext *TheContext)
: Context(TheContext) {}

bool UseAfterMoveFinder::find(Stmt *CodeBlock, const Expr *MovingCall,
const ValueDecl *MovedVariable,
UseAfterMove *TheUseAfterMove) {
std::optional<UseAfterMove>
UseAfterMoveFinder::find(Stmt *CodeBlock, const Expr *MovingCall,
const DeclRefExpr *MovedVariable) {
// 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
// lambda.
Expand All @@ -104,29 +111,45 @@ bool UseAfterMoveFinder::find(Stmt *CodeBlock, const Expr *MovingCall,
std::unique_ptr<CFG> TheCFG =
CFG::buildCFG(nullptr, CodeBlock, Context, Options);
if (!TheCFG)
return false;
return std::nullopt;

Sequence = std::make_unique<ExprSequence>(TheCFG.get(), CodeBlock, Context);
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);
auto TheUseAfterMove =
findInternal(MoveBlock, MovingCall, MovedVariable->getDecl());

if (TheUseAfterMove) {
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 TheUseAfterMove;
}

bool UseAfterMoveFinder::findInternal(const CFGBlock *Block,
const Expr *MovingCall,
const ValueDecl *MovedVariable,
UseAfterMove *TheUseAfterMove) {
std::optional<UseAfterMove>
UseAfterMoveFinder::findInternal(const CFGBlock *Block, const Expr *MovingCall,
const ValueDecl *MovedVariable) {
if (Visited.count(Block))
return false;
return std::nullopt;

// Mark the block as visited (except if this is the block containing the
// std::move() and it's being visited the first time).
Expand Down Expand Up @@ -165,17 +188,18 @@ bool UseAfterMoveFinder::findInternal(const CFGBlock *Block,
}

if (!HaveSavingReinit) {
TheUseAfterMove->DeclRef = Use;
UseAfterMove TheUseAfterMove;
TheUseAfterMove.DeclRef = Use;

// Is this a use-after-move that depends on order of evaluation?
// This is the case if the move potentially comes after the use (and we
// already know that use potentially comes after the move, which taken
// together tells us that the ordering is unclear).
TheUseAfterMove->EvaluationOrderUndefined =
TheUseAfterMove.EvaluationOrderUndefined =
MovingCall != nullptr &&
Sequence->potentiallyAfter(MovingCall, Use);

return true;
return TheUseAfterMove;
}
}
}
Expand All @@ -184,12 +208,15 @@ bool UseAfterMoveFinder::findInternal(const CFGBlock *Block,
// successors.
if (Reinits.empty()) {
for (const auto &Succ : Block->succs()) {
if (Succ && findInternal(Succ, nullptr, MovedVariable, TheUseAfterMove))
return true;
if (Succ) {
if (auto Found = findInternal(Succ, nullptr, MovedVariable)) {
return Found;
}
}
}
}

return false;
return std::nullopt;
}

void UseAfterMoveFinder::getUsesAndReinits(
Expand Down Expand Up @@ -394,7 +421,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 @@ -494,9 +521,8 @@ 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))
emitDiagnostic(MovingCall, Arg, Use, this, Result.Context,
if (auto Use = Finder.find(CodeBlock, MovingCall, Arg))
emitDiagnostic(MovingCall, Arg, *Use, this, Result.Context,
determineMoveType(MoveDecl));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ void CalleeNamespaceCheck::check(const MatchFinder::MatchResult &Result) {
// __llvm_libc, we're good.
const auto *NS = dyn_cast<NamespaceDecl>(getOutermostNamespace(FuncDecl));
if (NS && Result.SourceManager->isMacroBodyExpansion(NS->getLocation()) &&
NS->getName().starts_with(RequiredNamespaceStart))
NS->getName().starts_with(RequiredNamespaceRefStart))
return;

const DeclarationName &Name = FuncDecl->getDeclName();
Expand All @@ -62,7 +62,7 @@ void CalleeNamespaceCheck::check(const MatchFinder::MatchResult &Result) {
diag(UsageSiteExpr->getBeginLoc(),
"%0 must resolve to a function declared "
"within the namespace defined by the '%1' macro")
<< FuncDecl << RequiredNamespaceMacroName;
<< FuncDecl << RequiredNamespaceRefMacroName;

diag(FuncDecl->getLocation(), "resolves to this declaration",
clang::DiagnosticIDs::Note);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,35 @@ void ImplementationInNamespaceCheck::check(
const auto *MatchedDecl =
Result.Nodes.getNodeAs<Decl>("child_of_translation_unit");
const auto *NS = dyn_cast<NamespaceDecl>(MatchedDecl);

// LLVM libc declarations should be inside of a non-anonymous namespace.
if (NS == nullptr || NS->isAnonymousNamespace()) {
diag(MatchedDecl->getLocation(),
"declaration must be enclosed within the '%0' namespace")
<< RequiredNamespaceMacroName;
<< RequiredNamespaceDeclMacroName;
return;
}

// Enforce that the namespace is the result of macro expansion
if (Result.SourceManager->isMacroBodyExpansion(NS->getLocation()) == false) {
diag(NS->getLocation(), "the outermost namespace should be the '%0' macro")
<< RequiredNamespaceMacroName;
<< RequiredNamespaceDeclMacroName;
return;
}
if (NS->getName().starts_with(RequiredNamespaceStart) == false) {

// We want the macro to have [[gnu::visibility("hidden")]] as a prefix, but
// visibility is just an attribute in the AST construct, so we check that
// instead.
if (NS->getVisibility() != Visibility::HiddenVisibility) {
diag(NS->getLocation(), "the '%0' macro should start with '%1'")
<< RequiredNamespaceMacroName << RequiredNamespaceStart;
<< RequiredNamespaceDeclMacroName << RequiredNamespaceDeclStart;
return;
}

// Lastly, make sure the namespace name actually has the __llvm_libc prefix
if (NS->getName().starts_with(RequiredNamespaceRefStart) == false) {
diag(NS->getLocation(), "the '%0' macro expansion should start with '%1'")
<< RequiredNamespaceDeclMacroName << RequiredNamespaceRefStart;
return;
}
}
Expand Down
8 changes: 6 additions & 2 deletions clang-tools-extra/clang-tidy/llvmlibc/NamespaceConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@

namespace clang::tidy::llvm_libc {

const static llvm::StringRef RequiredNamespaceStart = "__llvm_libc";
const static llvm::StringRef RequiredNamespaceMacroName = "LIBC_NAMESPACE";
const static llvm::StringRef RequiredNamespaceRefStart = "__llvm_libc";
const static llvm::StringRef RequiredNamespaceRefMacroName = "LIBC_NAMESPACE";
const static llvm::StringRef RequiredNamespaceDeclStart =
"[[gnu::visibility(\"hidden\")]] __llvm_libc";
const static llvm::StringRef RequiredNamespaceDeclMacroName =
"LIBC_NAMESPACE_DECL";

} // namespace clang::tidy::llvm_libc
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
17 changes: 16 additions & 1 deletion clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ 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.

Expand Down Expand Up @@ -174,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 @@ -277,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"`).
2 changes: 2 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 @@ -301,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
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,30 @@ correct namespace.

.. code-block:: c++

// Implementation inside the LIBC_NAMESPACE namespace.
// Implementation inside the LIBC_NAMESPACE_DECL namespace.
// Correct if:
// - LIBC_NAMESPACE is a macro
// - LIBC_NAMESPACE expansion starts with `__llvm_libc`
namespace LIBC_NAMESPACE {
// - LIBC_NAMESPACE_DECL is a macro
// - LIBC_NAMESPACE_DECL expansion starts with `[[gnu::visibility("hidden")]] __llvm_libc`
namespace LIBC_NAMESPACE_DECL {
void LLVM_LIBC_ENTRYPOINT(strcpy)(char *dest, const char *src) {}
// Namespaces within LIBC_NAMESPACE namespace are allowed.
// Namespaces within LIBC_NAMESPACE_DECL namespace are allowed.
namespace inner {
int localVar = 0;
}
// Functions with C linkage are allowed.
extern "C" void str_fuzz() {}
}
// Incorrect: implementation not in the LIBC_NAMESPACE namespace.
// Incorrect: implementation not in the LIBC_NAMESPACE_DECL namespace.
void LLVM_LIBC_ENTRYPOINT(strcpy)(char *dest, const char *src) {}
// Incorrect: outer most namespace is not the LIBC_NAMESPACE macro.
// Incorrect: outer most namespace is not the LIBC_NAMESPACE_DECL macro.
namespace something_else {
void LLVM_LIBC_ENTRYPOINT(strcpy)(char *dest, const char *src) {}
}
// Incorrect: outer most namespace expansion does not start with `__llvm_libc`.
#define LIBC_NAMESPACE custom_namespace
namespace LIBC_NAMESPACE {
// Incorrect: outer most namespace expansion does not start with `[[gnu::visibility("hidden")]] __llvm_libc`.
#define LIBC_NAMESPACE_DECL custom_namespace
namespace LIBC_NAMESPACE_DECL {
void LLVM_LIBC_ENTRYPOINT(strcpy)(char *dest, const char *src) {}
}
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
@@ -1,3 +1,4 @@
// RUN: %check_clang_tidy -std=c++11 -check-suffixes=,CXX11 %s bugprone-use-after-move %t -- -- -fno-delayed-template-parsing
// RUN: %check_clang_tidy -std=c++17-or-later %s bugprone-use-after-move %t -- -- -fno-delayed-template-parsing

typedef decltype(nullptr) nullptr_t;
Expand Down Expand Up @@ -135,6 +136,7 @@ class A {
A &operator=(A &&);

void foo() const;
void bar(int i) const;
int getInt() const;

operator bool() const;
Expand Down Expand Up @@ -576,6 +578,19 @@ void useAndMoveInLoop() {
std::move(a);
}
}
// Same as above, but the use and the move are in different CFG blocks.
{
A a;
for (int i = 0; i < 10; ++i) {
if (i < 10)
a.foo();
// CHECK-NOTES: [[@LINE-1]]:9: warning: 'a' used after it was moved
// CHECK-NOTES: [[@LINE+3]]:9: note: move occurred here
// CHECK-NOTES: [[@LINE-3]]:9: note: the use happens in a later loop
if (i < 10)
std::move(a);
}
}
// However, this case shouldn't be flagged -- the scope of the declaration of
// 'a' is important.
{
Expand Down Expand Up @@ -1352,6 +1367,40 @@ void ifWhileAndSwitchSequenceInitDeclAndCondition() {
}
}

// In a function call, the expression that determines the callee is sequenced
// before the arguments -- but only in C++17 and later.
namespace CalleeSequencedBeforeArguments {
int consumeA(std::unique_ptr<A> a);
int consumeA(A &&a);

void calleeSequencedBeforeArguments() {
{
std::unique_ptr<A> a;
a->bar(consumeA(std::move(a)));
// CHECK-NOTES-CXX11: [[@LINE-1]]:5: warning: 'a' used after it was moved
// CHECK-NOTES-CXX11: [[@LINE-2]]:21: note: move occurred here
// CHECK-NOTES-CXX11: [[@LINE-3]]:5: note: the use and move are unsequenced
}
{
std::unique_ptr<A> a;
std::unique_ptr<A> getArg(std::unique_ptr<A> a);
getArg(std::move(a))->bar(a->getInt());
// CHECK-NOTES: [[@LINE-1]]:31: warning: 'a' used after it was moved
// CHECK-NOTES: [[@LINE-2]]:12: note: move occurred here
// CHECK-NOTES-CXX11: [[@LINE-3]]:31: note: the use and move are unsequenced
}
{
A a;
// Nominally, the callee `a.bar` is evaluated before the argument
// `consumeA(std::move(a))`, but in effect `a` is only accessed after the
// call to `A::bar()` happens, i.e. after the argument has been evaluted.
a.bar(consumeA(std::move(a)));
// CHECK-NOTES: [[@LINE-1]]:5: warning: 'a' used after it was moved
// CHECK-NOTES: [[@LINE-2]]:11: note: move occurred here
}
}
} // namespace CalleeSequencedBeforeArguments

// Some statements in templates (e.g. null, break and continue statements) may
// be shared between the uninstantiated and instantiated versions of the
// template and therefore have multiple parents. Make sure the sequencing code
Expand Down Expand Up @@ -1469,7 +1518,6 @@ class CtorInitOrder {
// CHECK-NOTES: [[@LINE-1]]:11: warning: 'val' used after it was moved
s{std::move(val)} {} // wrong order
// CHECK-NOTES: [[@LINE-1]]:9: note: move occurred here
// CHECK-NOTES: [[@LINE-4]]:11: note: the use happens in a later loop iteration than the move

private:
bool a;
Expand Down
Loading