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

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

if (DefJTBaseAdd->getOpcode() == AArch64::ADR) {
// TODO: Handle the pattern where there is no adrp/add pair.
// It also occurs when the binary is static.
// adr x13, 0x215a18 <_nl_value_type_LC_COLLATE+0x50>
// ldrh w13, [x13, w12, uxtw #1]
// adr x12, 0x247b30 <__gettextparse+0x5b0>
// add x13, x12, w13, sxth #2
// br x13
errs() << "BOLT-WARNING: Failed to match indirect branch: "
"nop/adr instead of adrp/add \n";
return false;
}

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

Expand Down
83 changes: 83 additions & 0 deletions bolt/test/AArch64/test-indirect-branch.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Test how BOLT handles indirect branch sequence of instructions in
// AArch64MCPlus builder.

// clang-format off

// REQUIRES: system-linux
// RUN: llvm-mc -filetype=obj -triple aarch64-unknown-unknown %s -o %t.o
// RUN: %clang %cflags --target=aarch64-unknown-linux %t.o -o %t.exe -Wl,-q
// RUN: llvm-bolt %t.exe -o %t.bolt --print-cfg --strict\
// RUN: -v=1 2>&1 | FileCheck %s

// Pattern 1: there is no shift amount after the 'add' instruction.
//
// adr x6, 0x219fb0 <sigall_set+0x88>
// add x6, x6, x14, lsl #2
// ldr w7, [x6]
// add x6, x6, w7, sxtw => no shift amount
// br x6
//

// Pattern 2: nop/adr pair is used in place of adrp/add
//
// nop => nop/adr instead of adrp/add
// adr x13, 0x215a18 <_nl_value_type_LC_COLLATE+0x50>
// ldrh w13, [x13, w12, uxtw #1]
// adr x12, 0x247b30 <__gettextparse+0x5b0>
// add x13, x12, w13, sxth #2
// br x13

.section .text
.align 4
.globl _start
.type _start, %function
_start:
bl test1
bl test2
// mov x0, #4
// mov w8, #93
// svc #0

// Pattern 1
// CHECK: BOLT-WARNING: Failed to match indirect branch: ShiftVAL != 2
.globl test1
.type test1, %function
test1:
mov x1, #0
adr x3, datatable
add x3, x3, x1, lsl #2
ldr w2, [x3]
add x3, x3, w2, sxtw
br x3
test1_0:
ret
test1_1:
ret
test1_2:
ret

// Pattern 2
// CHECK: BOLT-WARNING: Failed to match indirect branch: nop/adr instead of adrp/add
.globl test2
.type test2, %function
test2:
nop
adr x3, jump_table
ldrh w3, [x3, x1, lsl #1]
adr x1, test2_0
add x3, x1, w3, sxth #2
br x3
test2_0:
ret
test2_1:
ret

.section .rodata,"a",@progbits
datatable:
.word test1_0-datatable
.word test1_1-datatable
.word test1_2-datatable

jump_table:
.hword (test2_0-test2_0)>>2
.hword (test2_1-test2_0)>>2
2 changes: 1 addition & 1 deletion bolt/test/X86/infer_no_exits.test
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@

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

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

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

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

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

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

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

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

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

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

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

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

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

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

LINK_LIBS
Expand Down
371 changes: 371 additions & 0 deletions clang-tools-extra/clang-tidy/boost/UseRangesCheck.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,371 @@
//===--- UseRangesCheck.cpp - clang-tidy ----------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "UseRangesCheck.h"
#include "clang/AST/Decl.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/LLVM.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/IntrusiveRefCntPtr.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include <initializer_list>
#include <optional>
#include <string>

// FixItHint - Let the docs script know that this class does provide fixits

namespace clang::tidy::boost {

namespace {
/// Base replacer that handles the boost include path and namespace
class BoostReplacer : public UseRangesCheck::Replacer {
public:
BoostReplacer(ArrayRef<UseRangesCheck::Signature> Signatures,
bool IncludeSystem)
: Signatures(Signatures), IncludeSystem(IncludeSystem) {}

ArrayRef<UseRangesCheck::Signature> getReplacementSignatures() const final {
return Signatures;
}

virtual std::pair<StringRef, StringRef>
getBoostName(const NamedDecl &OriginalName) const = 0;

virtual std::pair<StringRef, StringRef>
getBoostHeader(const NamedDecl &OriginalName) const = 0;

std::optional<std::string>
getReplaceName(const NamedDecl &OriginalName) const final {
auto [Namespace, Function] = getBoostName(OriginalName);
return ("boost::" + Namespace + (Namespace.empty() ? "" : "::") + Function)
.str();
}

std::optional<std::string>
getHeaderInclusion(const NamedDecl &OriginalName) const final {
auto [Path, HeaderName] = getBoostHeader(OriginalName);
return ((IncludeSystem ? "<boost/" : "boost/") + Path +
(Path.empty() ? "" : "/") + HeaderName +
(IncludeSystem ? ".hpp>" : ".hpp"))
.str();
}

private:
SmallVector<UseRangesCheck::Signature> Signatures;
bool IncludeSystem;
};

/// Creates replaces where the header file lives in
/// `boost/algorithm/<FUNC_NAME>.hpp` and the function is named
/// `boost::range::<FUNC_NAME>`
class BoostRangeAlgorithmReplacer : public BoostReplacer {
public:
using BoostReplacer::BoostReplacer;

std::pair<StringRef, StringRef>
getBoostName(const NamedDecl &OriginalName) const override {
return {"range", OriginalName.getName()};
}

std::pair<StringRef, StringRef>
getBoostHeader(const NamedDecl &OriginalName) const override {
return {"range/algorithm", OriginalName.getName()};
}
};

/// Creates replaces where the header file lives in
/// `boost/algorithm/<CUSTOM_HEADER>.hpp` and the function is named
/// `boost::range::<FUNC_NAME>`
class CustomBoostAlgorithmHeaderReplacer : public BoostRangeAlgorithmReplacer {
public:
CustomBoostAlgorithmHeaderReplacer(
StringRef HeaderName, ArrayRef<UseRangesCheck::Signature> Signatures,
bool IncludeSystem)
: BoostRangeAlgorithmReplacer(Signatures, IncludeSystem),
HeaderName(HeaderName) {}

std::pair<StringRef, StringRef>
getBoostHeader(const NamedDecl & /*OriginalName*/) const override {
return {"range/algorithm", HeaderName};
}

private:
StringRef HeaderName;
};

/// Creates replaces where the header file lives in
/// `boost/algorithm/<SUB_HEADER>.hpp` and the function is named
/// `boost::algorithm::<FUNC_NAME>`
class BoostAlgorithmReplacer : public BoostReplacer {
public:
BoostAlgorithmReplacer(StringRef SubHeader,
ArrayRef<UseRangesCheck::Signature> Signatures,
bool IncludeSystem)
: BoostReplacer(Signatures, IncludeSystem),
SubHeader(("algorithm/" + SubHeader).str()) {}
std::pair<StringRef, StringRef>
getBoostName(const NamedDecl &OriginalName) const override {
return {"algorithm", OriginalName.getName()};
}

std::pair<StringRef, StringRef>
getBoostHeader(const NamedDecl &OriginalName) const override {
return {SubHeader, OriginalName.getName()};
}

private:
std::string SubHeader;
};

/// Creates replaces where the header file lives in
/// `boost/algorithm/<SUB_HEADER>/<HEADER_NAME>.hpp` and the function is named
/// `boost::algorithm::<FUNC_NAME>`
class CustomBoostAlgorithmReplacer : public BoostReplacer {
public:
CustomBoostAlgorithmReplacer(StringRef SubHeader, StringRef HeaderName,
ArrayRef<UseRangesCheck::Signature> Signatures,
bool IncludeSystem)
: BoostReplacer(Signatures, IncludeSystem),
SubHeader(("algorithm/" + SubHeader).str()), HeaderName(HeaderName) {}
std::pair<StringRef, StringRef>
getBoostName(const NamedDecl &OriginalName) const override {
return {"algorithm", OriginalName.getName()};
}

std::pair<StringRef, StringRef>
getBoostHeader(const NamedDecl & /*OriginalName*/) const override {
return {SubHeader, HeaderName};
}

private:
std::string SubHeader;
StringRef HeaderName;
};

/// A Replacer that is used for functions that just call a new overload
class MakeOverloadReplacer : public UseRangesCheck::Replacer {
public:
explicit MakeOverloadReplacer(ArrayRef<UseRangesCheck::Signature> Signatures)
: Signatures(Signatures) {}

ArrayRef<UseRangesCheck::Signature>
getReplacementSignatures() const override {
return Signatures;
}

std::optional<std::string>
getReplaceName(const NamedDecl & /* OriginalName */) const override {
return std::nullopt;
}

std::optional<std::string>
getHeaderInclusion(const NamedDecl & /* OriginalName */) const override {
return std::nullopt;
}

private:
SmallVector<UseRangesCheck::Signature> Signatures;
};

/// A replacer that replaces functions with an equivalent named function in the
/// root boost namespace
class FixedBoostReplace : public BoostReplacer {
public:
FixedBoostReplace(StringRef Header,
ArrayRef<UseRangesCheck::Signature> Signatures,
bool IncludeBoostSystem)
: BoostReplacer(Signatures, IncludeBoostSystem), Header(Header) {}

std::pair<StringRef, StringRef>
getBoostName(const NamedDecl &OriginalName) const override {
return {{}, OriginalName.getName()};
}

std::pair<StringRef, StringRef>
getBoostHeader(const NamedDecl & /* OriginalName */) const override {
return {{}, Header};
}

private:
StringRef Header;
};

} // namespace

utils::UseRangesCheck::ReplacerMap UseRangesCheck::getReplacerMap() const {

ReplacerMap Results;
static const Signature SingleSig = {{0}};
static const Signature TwoSig = {{0}, {2}};
static const auto AddFrom =
[&Results](llvm::IntrusiveRefCntPtr<UseRangesCheck::Replacer> Replacer,
std::initializer_list<StringRef> Names, StringRef Prefix) {
llvm::SmallString<64> Buffer;
for (const auto &Name : Names) {
Buffer.assign({"::", Prefix, (Prefix.empty() ? "" : "::"), Name});
Results.try_emplace(Buffer, Replacer);
}
};

static const auto AddFromStd =
[](llvm::IntrusiveRefCntPtr<UseRangesCheck::Replacer> Replacer,
std::initializer_list<StringRef> Names) {
AddFrom(Replacer, Names, "std");
};

static const auto AddFromBoost =
[](llvm::IntrusiveRefCntPtr<UseRangesCheck::Replacer> Replacer,
std::initializer_list<
std::pair<StringRef, std::initializer_list<StringRef>>>
NamespaceAndNames) {
for (auto [Namespace, Names] : NamespaceAndNames)
AddFrom(Replacer, Names,
SmallString<64>{"boost", (Namespace.empty() ? "" : "::"),
Namespace});
};

AddFromStd(llvm::makeIntrusiveRefCnt<CustomBoostAlgorithmHeaderReplacer>(
"set_algorithm", TwoSig, IncludeBoostSystem),
{"includes", "set_union", "set_intersection", "set_difference",
"set_symmetric_difference"});

AddFromStd(llvm::makeIntrusiveRefCnt<BoostRangeAlgorithmReplacer>(
SingleSig, IncludeBoostSystem),
{"unique", "lower_bound", "stable_sort",
"equal_range", "remove_if", "sort",
"random_shuffle", "remove_copy", "stable_partition",
"remove_copy_if", "count", "copy_backward",
"reverse_copy", "adjacent_find", "remove",
"upper_bound", "binary_search", "replace_copy_if",
"for_each", "generate", "count_if",
"min_element", "reverse", "replace_copy",
"fill", "unique_copy", "transform",
"copy", "replace", "find",
"replace_if", "find_if", "partition",
"max_element"});

AddFromStd(llvm::makeIntrusiveRefCnt<BoostRangeAlgorithmReplacer>(
TwoSig, IncludeBoostSystem),
{"find_end", "merge", "partial_sort_copy", "find_first_of",
"search", "lexicographical_compare", "equal", "mismatch"});

AddFromStd(llvm::makeIntrusiveRefCnt<CustomBoostAlgorithmHeaderReplacer>(
"permutation", SingleSig, IncludeBoostSystem),
{"next_permutation", "prev_permutation"});

AddFromStd(llvm::makeIntrusiveRefCnt<CustomBoostAlgorithmHeaderReplacer>(
"heap_algorithm", SingleSig, IncludeBoostSystem),
{"push_heap", "pop_heap", "make_heap", "sort_heap"});

AddFromStd(llvm::makeIntrusiveRefCnt<BoostAlgorithmReplacer>(
"cxx11", SingleSig, IncludeBoostSystem),
{"copy_if", "is_permutation", "is_partitioned", "find_if_not",
"partition_copy", "any_of", "iota", "all_of", "partition_point",
"is_sorted", "none_of"});

AddFromStd(llvm::makeIntrusiveRefCnt<CustomBoostAlgorithmReplacer>(
"cxx11", "is_sorted", SingleSig, IncludeBoostSystem),
{"is_sorted_until"});

AddFromStd(llvm::makeIntrusiveRefCnt<FixedBoostReplace>(
"range/numeric", SingleSig, IncludeBoostSystem),
{"accumulate", "partial_sum", "adjacent_difference"});

if (getLangOpts().CPlusPlus17)
AddFromStd(llvm::makeIntrusiveRefCnt<BoostAlgorithmReplacer>(
"cxx17", SingleSig, IncludeBoostSystem),
{"reduce"});

AddFromBoost(llvm::makeIntrusiveRefCnt<MakeOverloadReplacer>(SingleSig),
{{"algorithm",
{"reduce",
"find_backward",
"find_not_backward",
"find_if_backward",
"find_if_not_backward",
"hex",
"hex_lower",
"unhex",
"is_partitioned_until",
"is_palindrome",
"copy_if",
"copy_while",
"copy_until",
"copy_if_while",
"copy_if_until",
"is_permutation",
"is_partitioned",
"one_of",
"one_of_equal",
"find_if_not",
"partition_copy",
"any_of",
"any_of_equal",
"iota",
"all_of",
"all_of_equal",
"partition_point",
"is_sorted_until",
"is_sorted",
"is_increasing",
"is_decreasing",
"is_strictly_increasing",
"is_strictly_decreasing",
"none_of",
"none_of_equal",
"clamp_range"}}});

AddFromBoost(
llvm::makeIntrusiveRefCnt<MakeOverloadReplacer>(TwoSig),
{{"algorithm", {"apply_permutation", "apply_reverse_permutation"}}});

return Results;
}

UseRangesCheck::UseRangesCheck(StringRef Name, ClangTidyContext *Context)
: utils::UseRangesCheck(Name, Context),
IncludeBoostSystem(Options.get("IncludeBoostSystem", true)) {}

void UseRangesCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
utils::UseRangesCheck::storeOptions(Opts);
Options.store(Opts, "IncludeBoostSystem", IncludeBoostSystem);
}
DiagnosticBuilder UseRangesCheck::createDiag(const CallExpr &Call) {
DiagnosticBuilder D =
diag(Call.getBeginLoc(), "use a %0 version of this algorithm");
D << (Call.getDirectCallee()->isInStdNamespace() ? "boost" : "ranged");
return D;
}
ArrayRef<std::pair<StringRef, StringRef>>
UseRangesCheck::getFreeBeginEndMethods() const {
static const std::pair<StringRef, StringRef> Refs[] = {
{"::std::begin", "::std::end"},
{"::std::cbegin", "::std::cend"},
{"::boost::range_adl_barrier::begin", "::boost::range_adl_barrier::end"},
{"::boost::range_adl_barrier::const_begin",
"::boost::range_adl_barrier::const_end"},
};
return Refs;
}
std::optional<UseRangesCheck::ReverseIteratorDescriptor>
UseRangesCheck::getReverseDescriptor() const {
static const std::pair<StringRef, StringRef> Refs[] = {
{"::std::rbegin", "::std::rend"},
{"::std::crbegin", "::std::crend"},
{"::boost::rbegin", "::boost::rend"},
{"::boost::const_rbegin", "::boost::const_rend"},
};
return ReverseIteratorDescriptor{"boost::adaptors::reverse",
IncludeBoostSystem
? "<boost/range/adaptor/reversed.hpp>"
: "boost/range/adaptor/reversed.hpp",
Refs};
}
} // namespace clang::tidy::boost
43 changes: 43 additions & 0 deletions clang-tools-extra/clang-tidy/boost/UseRangesCheck.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//===--- UseRangesCheck.h - clang-tidy --------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BOOST_USERANGESCHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BOOST_USERANGESCHECK_H

#include "../utils/UseRangesCheck.h"

namespace clang::tidy::boost {

/// Detects calls to standard library iterator algorithms that could be
/// replaced with a boost ranges version instead
///
/// For the user-facing documentation see:
/// http://clang.llvm.org/extra/clang-tidy/checks/boost/use-ranges.html
class UseRangesCheck : public utils::UseRangesCheck {
public:
UseRangesCheck(StringRef Name, ClangTidyContext *Context);

void storeOptions(ClangTidyOptions::OptionMap &Options) override;

ReplacerMap getReplacerMap() const override;

DiagnosticBuilder createDiag(const CallExpr &Call) override;

ArrayRef<std::pair<StringRef, StringRef>>
getFreeBeginEndMethods() const override;

std::optional<ReverseIteratorDescriptor>
getReverseDescriptor() const override;

private:
bool IncludeBoostSystem;
};

} // namespace clang::tidy::boost

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BOOST_USERANGESCHECK_H
42 changes: 33 additions & 9 deletions clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Analysis/Analyses/CFGReachabilityAnalysis.h"
#include "clang/Analysis/CFG.h"
#include "clang/Lex/Lexer.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallPtrSet.h"

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

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

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

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

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

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

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

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

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

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

DiagnosticBuilder DB = diag(VD->getLocation(), Message) << "variable" << VD;
SourceLocation FixLoc = VD->getTypeSpecStartLoc();
if (FixLoc.isInvalid() || FixLoc.isMacroID())
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ add_clang_library(clangTidyModernizeModule
UseNoexceptCheck.cpp
UseNullptrCheck.cpp
UseOverrideCheck.cpp
UseRangesCheck.cpp
UseStartsEndsWithCheck.cpp
UseStdFormatCheck.cpp
UseStdNumbersCheck.cpp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include "UseNoexceptCheck.h"
#include "UseNullptrCheck.h"
#include "UseOverrideCheck.h"
#include "UseRangesCheck.h"
#include "UseStartsEndsWithCheck.h"
#include "UseStdFormatCheck.h"
#include "UseStdNumbersCheck.h"
Expand Down Expand Up @@ -75,6 +76,7 @@ class ModernizeModule : public ClangTidyModule {
CheckFactories.registerCheck<PassByValueCheck>("modernize-pass-by-value");
CheckFactories.registerCheck<UseDesignatedInitializersCheck>(
"modernize-use-designated-initializers");
CheckFactories.registerCheck<UseRangesCheck>("modernize-use-ranges");
CheckFactories.registerCheck<UseStartsEndsWithCheck>(
"modernize-use-starts-ends-with");
CheckFactories.registerCheck<UseStdFormatCheck>("modernize-use-std-format");
Expand Down
185 changes: 185 additions & 0 deletions clang-tools-extra/clang-tidy/modernize/UseRangesCheck.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
//===--- UseRangesCheck.cpp - clang-tidy ----------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "UseRangesCheck.h"
#include "clang/AST/Decl.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/IntrusiveRefCntPtr.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include <initializer_list>

// FixItHint - Let the docs script know that this class does provide fixits

namespace clang::tidy::modernize {

static constexpr const char *SingleRangeNames[] = {
"all_of",
"any_of",
"none_of",
"for_each",
"find",
"find_if",
"find_if_not",
"adjacent_find",
"copy",
"copy_if",
"copy_backward",
"move",
"move_backward",
"fill",
"transform",
"replace",
"replace_if",
"generate",
"remove",
"remove_if",
"remove_copy",
"remove_copy_if",
"unique",
"unique_copy",
"sample",
"partition_point",
"lower_bound",
"upper_bound",
"equal_range",
"binary_search",
"push_heap",
"pop_heap",
"make_heap",
"sort_heap",
"next_permutation",
"prev_permutation",
"reverse",
"reverse_copy",
"shift_left",
"shift_right",
"is_partitioned",
"partition",
"partition_copy",
"stable_partition",
"sort",
"stable_sort",
"is_sorted",
"is_sorted_until",
"is_heap",
"is_heap_until",
"max_element",
"min_element",
"minmax_element",
"uninitialized_copy",
"uninitialized_fill",
"uninitialized_move",
"uninitialized_default_construct",
"uninitialized_value_construct",
"destroy",
};

static constexpr const char *TwoRangeNames[] = {
"equal",
"mismatch",
"partial_sort_copy",
"includes",
"set_union",
"set_intersection",
"set_difference",
"set_symmetric_difference",
"merge",
"lexicographical_compare",
"find_end",
"search",
"is_permutation",
};

namespace {
class StdReplacer : public utils::UseRangesCheck::Replacer {
public:
explicit StdReplacer(SmallVector<UseRangesCheck::Signature> Signatures)
: Signatures(std::move(Signatures)) {}
std::optional<std::string>
getReplaceName(const NamedDecl &OriginalName) const override {
return ("std::ranges::" + OriginalName.getName()).str();
}
ArrayRef<UseRangesCheck::Signature>
getReplacementSignatures() const override {
return Signatures;
}

private:
SmallVector<UseRangesCheck::Signature> Signatures;
};

class StdAlgorithmReplacer : public StdReplacer {
using StdReplacer::StdReplacer;
std::optional<std::string>
getHeaderInclusion(const NamedDecl & /*OriginalName*/) const override {
return "<algorithm>";
}
};

class StdNumericReplacer : public StdReplacer {
using StdReplacer::StdReplacer;
std::optional<std::string>
getHeaderInclusion(const NamedDecl & /*OriginalName*/) const override {
return "<numeric>";
}
};
} // namespace

utils::UseRangesCheck::ReplacerMap UseRangesCheck::getReplacerMap() const {

utils::UseRangesCheck::ReplacerMap Result;

// template<typename Iter> Func(Iter first, Iter last,...).
static const Signature SingleRangeArgs = {{0}};
// template<typename Iter1, typename Iter2>
// Func(Iter1 first1, Iter1 last1, Iter2 first2, Iter2 last2,...).
static const Signature TwoRangeArgs = {{0}, {2}};

static const Signature SingleRangeFunc[] = {SingleRangeArgs};

static const Signature TwoRangeFunc[] = {TwoRangeArgs};

static const std::pair<ArrayRef<Signature>, ArrayRef<const char *>>
AlgorithmNames[] = {{SingleRangeFunc, SingleRangeNames},
{TwoRangeFunc, TwoRangeNames}};
SmallString<64> Buff;
for (const auto &[Signatures, Values] : AlgorithmNames) {
auto Replacer = llvm::makeIntrusiveRefCnt<StdAlgorithmReplacer>(
SmallVector<UseRangesCheck::Signature>{Signatures});
for (const auto &Name : Values) {
Buff.assign({"::std::", Name});
Result.try_emplace(Buff, Replacer);
}
}
if (getLangOpts().CPlusPlus23)
Result.try_emplace(
"::std::iota",
llvm::makeIntrusiveRefCnt<StdNumericReplacer>(
SmallVector<UseRangesCheck::Signature>{std::begin(SingleRangeFunc),
std::end(SingleRangeFunc)}));
return Result;
}

bool UseRangesCheck::isLanguageVersionSupported(
const LangOptions &LangOpts) const {
return LangOpts.CPlusPlus20;
}
ArrayRef<std::pair<StringRef, StringRef>>
UseRangesCheck::getFreeBeginEndMethods() const {
static const std::pair<StringRef, StringRef> Refs[] = {
{"::std::begin", "::std::end"}, {"::std::cbegin", "::std::cend"}};
return Refs;
}
std::optional<UseRangesCheck::ReverseIteratorDescriptor>
UseRangesCheck::getReverseDescriptor() const {
static const std::pair<StringRef, StringRef> Refs[] = {
{"::std::rbegin", "::std::rend"}, {"::std::crbegin", "::std::crend"}};
return ReverseIteratorDescriptor{"std::views::reverse", "<ranges>", Refs};
}
} // namespace clang::tidy::modernize
38 changes: 38 additions & 0 deletions clang-tools-extra/clang-tidy/modernize/UseRangesCheck.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//===--- UseRangesCheck.h - clang-tidy --------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USERANGESCHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USERANGESCHECK_H

#include "../utils/UseRangesCheck.h"

namespace clang::tidy::modernize {

/// Detects calls to standard library iterator algorithms that could be
/// replaced with a ranges version instead
///
/// For the user-facing documentation see:
/// http://clang.llvm.org/extra/clang-tidy/checks/modernize/use-ranges.html
class UseRangesCheck : public utils::UseRangesCheck {
public:
using utils::UseRangesCheck::UseRangesCheck;

ReplacerMap getReplacerMap() const override;

ArrayRef<std::pair<StringRef, StringRef>>
getFreeBeginEndMethods() const override;

std::optional<ReverseIteratorDescriptor>
getReverseDescriptor() const override;

bool isLanguageVersionSupported(const LangOptions &LangOpts) const override;
};

} // namespace clang::tidy::modernize

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USERANGESCHECK_H
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/utils/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ add_clang_library(clangTidyUtils
RenamerClangTidyCheck.cpp
TransformerClangTidyCheck.cpp
TypeTraits.cpp
UseRangesCheck.cpp
UsingInserter.cpp

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

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

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

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

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

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

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

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

// If 'After' is a parent of 'Before' or is sequenced after one of these
// parents, we know that it is sequenced after 'Before'.
for (const Stmt *Parent : getParentStmts(Before, Context)) {
for (const Stmt *Parent : BeforeParents) {
if (Parent == After || inSequence(Parent, After))
return true;
}
Expand Down
306 changes: 306 additions & 0 deletions clang-tools-extra/clang-tidy/utils/UseRangesCheck.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
//===--- UseRangesCheck.cpp - clang-tidy ----------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "UseRangesCheck.h"
#include "Matchers.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/Expr.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchersInternal.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/LLVM.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Lex/Lexer.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallBitVector.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/raw_ostream.h"
#include <cassert>
#include <optional>
#include <string>

using namespace clang::ast_matchers;

static constexpr const char BoundCall[] = "CallExpr";
static constexpr const char FuncDecl[] = "FuncDecl";
static constexpr const char ArgName[] = "ArgName";

namespace clang::tidy::utils {

static bool operator==(const UseRangesCheck::Indexes &L,
const UseRangesCheck::Indexes &R) {
return std::tie(L.BeginArg, L.EndArg, L.ReplaceArg) ==
std::tie(R.BeginArg, R.EndArg, R.ReplaceArg);
}

static std::string getFullPrefix(ArrayRef<UseRangesCheck::Indexes> Signature) {
std::string Output;
llvm::raw_string_ostream OS(Output);
for (const UseRangesCheck::Indexes &Item : Signature)
OS << Item.BeginArg << ":" << Item.EndArg << ":"
<< (Item.ReplaceArg == Item.First ? '0' : '1');
return Output;
}

static llvm::hash_code hash_value(const UseRangesCheck::Indexes &Indexes) {
return llvm::hash_combine(Indexes.BeginArg, Indexes.EndArg,
Indexes.ReplaceArg);
}

static llvm::hash_code hash_value(const UseRangesCheck::Signature &Sig) {
return llvm::hash_combine_range(Sig.begin(), Sig.end());
}

namespace {

AST_MATCHER(Expr, hasSideEffects) {
return Node.HasSideEffects(Finder->getASTContext());
}
} // namespace

static auto
makeExprMatcher(ast_matchers::internal::Matcher<Expr> ArgumentMatcher,
ArrayRef<StringRef> MethodNames,
ArrayRef<StringRef> FreeNames) {
return expr(
anyOf(cxxMemberCallExpr(argumentCountIs(0),
callee(cxxMethodDecl(hasAnyName(MethodNames))),
on(ArgumentMatcher)),
callExpr(argumentCountIs(1), hasArgument(0, ArgumentMatcher),
hasDeclaration(functionDecl(hasAnyName(FreeNames))))));
}

static ast_matchers::internal::Matcher<CallExpr>
makeMatcherPair(StringRef State, const UseRangesCheck::Indexes &Indexes,
ArrayRef<StringRef> BeginFreeNames,
ArrayRef<StringRef> EndFreeNames,
const std::optional<UseRangesCheck::ReverseIteratorDescriptor>
&ReverseDescriptor) {
std::string ArgBound = (ArgName + llvm::Twine(Indexes.BeginArg)).str();
SmallString<64> ID = {BoundCall, State};
ast_matchers::internal::Matcher<CallExpr> ArgumentMatcher = allOf(
hasArgument(Indexes.BeginArg,
makeExprMatcher(expr(unless(hasSideEffects())).bind(ArgBound),
{"begin", "cbegin"}, BeginFreeNames)),
hasArgument(Indexes.EndArg,
makeExprMatcher(
expr(matchers::isStatementIdenticalToBoundNode(ArgBound)),
{"end", "cend"}, EndFreeNames)));
if (ReverseDescriptor) {
ArgBound.push_back('R');
SmallVector<StringRef> RBegin{
llvm::make_first_range(ReverseDescriptor->FreeReverseNames)};
SmallVector<StringRef> REnd{
llvm::make_second_range(ReverseDescriptor->FreeReverseNames)};
ArgumentMatcher = anyOf(
ArgumentMatcher,
allOf(hasArgument(
Indexes.BeginArg,
makeExprMatcher(expr(unless(hasSideEffects())).bind(ArgBound),
{"rbegin", "crbegin"}, RBegin)),
hasArgument(
Indexes.EndArg,
makeExprMatcher(
expr(matchers::isStatementIdenticalToBoundNode(ArgBound)),
{"rend", "crend"}, REnd))));
}
return callExpr(argumentCountAtLeast(
std::max(Indexes.BeginArg, Indexes.EndArg) + 1),
ArgumentMatcher)
.bind(ID);
}

void UseRangesCheck::registerMatchers(MatchFinder *Finder) {
Replaces = getReplacerMap();
ReverseDescriptor = getReverseDescriptor();
auto BeginEndNames = getFreeBeginEndMethods();
llvm::SmallVector<StringRef, 4> BeginNames{
llvm::make_first_range(BeginEndNames)};
llvm::SmallVector<StringRef, 4> EndNames{
llvm::make_second_range(BeginEndNames)};
llvm::DenseSet<ArrayRef<Signature>> Seen;
for (auto I = Replaces.begin(), E = Replaces.end(); I != E; ++I) {
const ArrayRef<Signature> &Signatures =
I->getValue()->getReplacementSignatures();
if (!Seen.insert(Signatures).second)
continue;
assert(!Signatures.empty() &&
llvm::all_of(Signatures, [](auto Index) { return !Index.empty(); }));
std::vector<StringRef> Names(1, I->getKey());
for (auto J = std::next(I); J != E; ++J)
if (J->getValue()->getReplacementSignatures() == Signatures)
Names.push_back(J->getKey());

std::vector<ast_matchers::internal::DynTypedMatcher> TotalMatchers;
// As we match on the first matched signature, we need to sort the
// signatures in order of length(longest to shortest). This way any
// signature that is a subset of another signature will be matched after the
// other.
SmallVector<Signature> SigVec(Signatures);
llvm::sort(SigVec, [](auto &L, auto &R) { return R.size() < L.size(); });
for (const auto &Signature : SigVec) {
std::vector<ast_matchers::internal::DynTypedMatcher> Matchers;
for (const auto &ArgPair : Signature)
Matchers.push_back(makeMatcherPair(getFullPrefix(Signature), ArgPair,
BeginNames, EndNames,
ReverseDescriptor));
TotalMatchers.push_back(
ast_matchers::internal::DynTypedMatcher::constructVariadic(
ast_matchers::internal::DynTypedMatcher::VO_AllOf,
ASTNodeKind::getFromNodeKind<CallExpr>(), std::move(Matchers)));
}
Finder->addMatcher(
callExpr(
callee(functionDecl(hasAnyName(std::move(Names))).bind(FuncDecl)),
ast_matchers::internal::DynTypedMatcher::constructVariadic(
ast_matchers::internal::DynTypedMatcher::VO_AnyOf,
ASTNodeKind::getFromNodeKind<CallExpr>(),
std::move(TotalMatchers))
.convertTo<CallExpr>()),
this);
}
}

static void removeFunctionArgs(DiagnosticBuilder &Diag, const CallExpr &Call,
ArrayRef<unsigned> Indexes,
const ASTContext &Ctx) {
llvm::SmallVector<unsigned> Sorted(Indexes);
llvm::sort(Sorted);
// Keep track of commas removed
llvm::SmallBitVector Commas(Call.getNumArgs());
// The first comma is actually the '(' which we can't remove
Commas[0] = true;
for (unsigned Index : Sorted) {
const Expr *Arg = Call.getArg(Index);
if (Commas[Index]) {
if (Index >= Commas.size()) {
Diag << FixItHint::CreateRemoval(Arg->getSourceRange());
} else {
// Remove the next comma
Commas[Index + 1] = true;
Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange(
{Arg->getBeginLoc(),
Lexer::getLocForEndOfToken(
Arg->getEndLoc(), 0, Ctx.getSourceManager(), Ctx.getLangOpts())
.getLocWithOffset(1)}));
}
} else {
Diag << FixItHint::CreateRemoval(CharSourceRange::getTokenRange(
Arg->getBeginLoc().getLocWithOffset(-1), Arg->getEndLoc()));
Commas[Index] = true;
}
}
}

void UseRangesCheck::check(const MatchFinder::MatchResult &Result) {
const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>(FuncDecl);
std::string Qualified = "::" + Function->getQualifiedNameAsString();
auto Iter = Replaces.find(Qualified);
assert(Iter != Replaces.end());
SmallString<64> Buffer;
for (const Signature &Sig : Iter->getValue()->getReplacementSignatures()) {
Buffer.assign({BoundCall, getFullPrefix(Sig)});
const auto *Call = Result.Nodes.getNodeAs<CallExpr>(Buffer);
if (!Call)
continue;
auto Diag = createDiag(*Call);
if (auto ReplaceName = Iter->getValue()->getReplaceName(*Function))
Diag << FixItHint::CreateReplacement(Call->getCallee()->getSourceRange(),
*ReplaceName);
if (auto Include = Iter->getValue()->getHeaderInclusion(*Function))
Diag << Inserter.createIncludeInsertion(
Result.SourceManager->getFileID(Call->getBeginLoc()), *Include);
llvm::SmallVector<unsigned, 3> ToRemove;
for (const auto &[First, Second, Replace] : Sig) {
auto ArgNode = ArgName + std::to_string(First);
if (const auto *ArgExpr = Result.Nodes.getNodeAs<Expr>(ArgNode)) {
Diag << FixItHint::CreateReplacement(
Call->getArg(Replace == Indexes::Second ? Second : First)
->getSourceRange(),
Lexer::getSourceText(
CharSourceRange::getTokenRange(ArgExpr->getSourceRange()),
Result.Context->getSourceManager(),
Result.Context->getLangOpts()));
} else {
assert(ReverseDescriptor && "Couldn't find forward argument");
ArgNode.push_back('R');
ArgExpr = Result.Nodes.getNodeAs<Expr>(ArgNode);
assert(ArgExpr && "Couldn't find forward or reverse argument");
if (ReverseDescriptor->ReverseHeader)
Diag << Inserter.createIncludeInsertion(
Result.SourceManager->getFileID(Call->getBeginLoc()),
*ReverseDescriptor->ReverseHeader);
Diag << FixItHint::CreateReplacement(
Call->getArg(Replace == Indexes::Second ? Second : First)
->getSourceRange(),
SmallString<128>{
ReverseDescriptor->ReverseAdaptorName, "(",
Lexer::getSourceText(
CharSourceRange::getTokenRange(ArgExpr->getSourceRange()),
Result.Context->getSourceManager(),
Result.Context->getLangOpts()),
")"});
}
ToRemove.push_back(Replace == Indexes::Second ? First : Second);
}
removeFunctionArgs(Diag, *Call, ToRemove, *Result.Context);
return;
}
llvm_unreachable("No valid signature found");
}

bool UseRangesCheck::isLanguageVersionSupported(
const LangOptions &LangOpts) const {
return LangOpts.CPlusPlus11;
}

UseRangesCheck::UseRangesCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
Inserter(Options.getLocalOrGlobal("IncludeStyle",
utils::IncludeSorter::IS_LLVM),
areDiagsSelfContained()) {}

void UseRangesCheck::registerPPCallbacks(const SourceManager &,
Preprocessor *PP, Preprocessor *) {
Inserter.registerPreprocessor(PP);
}

void UseRangesCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "IncludeStyle", Inserter.getStyle());
}

std::optional<std::string>
UseRangesCheck::Replacer::getHeaderInclusion(const NamedDecl &) const {
return std::nullopt;
}

DiagnosticBuilder UseRangesCheck::createDiag(const CallExpr &Call) {
return diag(Call.getBeginLoc(), "use a ranges version of this algorithm");
}

std::optional<UseRangesCheck::ReverseIteratorDescriptor>
UseRangesCheck::getReverseDescriptor() const {
return std::nullopt;
}

ArrayRef<std::pair<StringRef, StringRef>>
UseRangesCheck::getFreeBeginEndMethods() const {
return {};
}

std::optional<TraversalKind> UseRangesCheck::getCheckTraversalKind() const {
return TK_IgnoreUnlessSpelledInSource;
}
} // namespace clang::tidy::utils
94 changes: 94 additions & 0 deletions clang-tools-extra/clang-tidy/utils/UseRangesCheck.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//===--- UseRangesCheck.h - clang-tidy --------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_USERANGESCHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_USERANGESCHECK_H

#include "../ClangTidyCheck.h"
#include "IncludeInserter.h"
#include "clang/AST/Decl.h"
#include "clang/AST/Expr.h"
#include "clang/Basic/Diagnostic.h"
#include "llvm/ADT/IntrusiveRefCntPtr.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
#include <optional>

namespace clang::tidy::utils {

/// Base class for handling converting std iterator algorithms to a range
/// equivalent.
class UseRangesCheck : public ClangTidyCheck {
public:
struct Indexes {
enum Replace { First, Second };
unsigned BeginArg;
unsigned EndArg = BeginArg + 1;
Replace ReplaceArg = First;
};

using Signature = SmallVector<Indexes, 2>;

struct ReverseIteratorDescriptor {
StringRef ReverseAdaptorName;
std::optional<StringRef> ReverseHeader;
ArrayRef<std::pair<StringRef, StringRef>> FreeReverseNames;
};

class Replacer : public llvm::RefCountedBase<Replacer> {
public:
/// Gets the name to replace a function with, return std::nullopt for a
/// replacement where we just call a different overload.
virtual std::optional<std::string>
getReplaceName(const NamedDecl &OriginalName) const = 0;

/// Gets the header needed to access the replaced function
/// Return std::nullopt if no new header is needed.
virtual std::optional<std::string>
getHeaderInclusion(const NamedDecl &OriginalName) const;

/// Gets an array of all the possible overloads for a function with indexes
/// where begin and end arguments are.
virtual ArrayRef<Signature> getReplacementSignatures() const = 0;
virtual ~Replacer() = default;
};

using ReplacerMap = llvm::StringMap<llvm::IntrusiveRefCntPtr<Replacer>>;

UseRangesCheck(StringRef Name, ClangTidyContext *Context);
/// Gets a map of function to replace and methods to create the replacements
virtual ReplacerMap getReplacerMap() const = 0;
/// Create a diagnostic for the CallExpr
/// Override this to support custom diagnostic messages
virtual DiagnosticBuilder createDiag(const CallExpr &Call);

virtual std::optional<ReverseIteratorDescriptor> getReverseDescriptor() const;

/// Gets the fully qualified names of begin and end functions.
/// The functions must take the container as their one and only argument
/// `::std::begin` and `::std::end` are a common example
virtual ArrayRef<std::pair<StringRef, StringRef>>
getFreeBeginEndMethods() const;

void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
Preprocessor *ModuleExpanderPP) final;
void registerMatchers(ast_matchers::MatchFinder *Finder) final;
void check(const ast_matchers::MatchFinder::MatchResult &Result) final;
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override;
void storeOptions(ClangTidyOptions::OptionMap &Options) override;
std::optional<TraversalKind> getCheckTraversalKind() const override;

private:
ReplacerMap Replaces;
std::optional<ReverseIteratorDescriptor> ReverseDescriptor;
IncludeInserter Inserter;
};

} // namespace clang::tidy::utils

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_USERANGESCHECK_H
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/support/ThreadsafeFS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class VolatileFileSystem : public llvm::vfs::ProxyFileSystem {
llvm::StringRef FileName = llvm::sys::path::filename(Path);
if (FileName.starts_with("preamble-") && FileName.ends_with(".pch"))
return File;
return std::unique_ptr<VolatileFile>(new VolatileFile(std::move(*File)));
return std::make_unique<VolatileFile>(std::move(*File));
}

private:
Expand Down
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
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
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,6 @@ int global_in_extern_c_1;
}

extern "C" int global_in_extern_c_2;

const int const_global = 123;
constexpr int constexpr_global = 123;
208 changes: 208 additions & 0 deletions clang-tools-extra/test/clang-tidy/checkers/modernize/use-ranges.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
// RUN: %check_clang_tidy -std=c++20 %s modernize-use-ranges %t
// RUN: %check_clang_tidy -std=c++23 %s modernize-use-ranges %t -check-suffixes=,CPP23

// CHECK-FIXES: #include <algorithm>
// CHECK-FIXES-CPP23: #include <numeric>
// CHECK-FIXES: #include <ranges>

namespace std {

template <typename T> class vector {
public:
using iterator = T *;
using const_iterator = const T *;
using reverse_iterator = T*;
using reverse_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();
constexpr reverse_const_iterator rbegin() const;
constexpr reverse_const_iterator rend() const;
constexpr reverse_const_iterator crbegin() const;
constexpr reverse_const_iterator crend() const;
constexpr reverse_iterator rbegin();
constexpr reverse_iterator rend();
};

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

template <typename Container> constexpr auto rbegin(const Container &Cont) {
return Cont.rbegin();
}

template <typename Container> constexpr auto rbegin(Container &Cont) {
return Cont.rbegin();
}

template <typename Container> constexpr auto rend(const Container &Cont) {
return Cont.rend();
}

template <typename Container> constexpr auto rend(Container &Cont) {
return Cont.rend();
}

template <typename Container> constexpr auto crbegin(const Container &Cont) {
return Cont.crbegin();
}

template <typename Container> constexpr auto crend(const Container &Cont) {
return Cont.crend();
}
// Find
template< class InputIt, class T >
InputIt find( InputIt first, InputIt last, const T& value );

// Reverse
template <typename Iter> void reverse(Iter begin, Iter end);

// Includes
template <class InputIt1, class InputIt2>
bool includes(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2);

// IsPermutation
template <class ForwardIt1, class ForwardIt2>
bool is_permutation(ForwardIt1 first1, ForwardIt1 last1, ForwardIt2 first2);
template <class ForwardIt1, class ForwardIt2>
bool is_permutation(ForwardIt1 first1, ForwardIt1 last1, ForwardIt2 first2,
ForwardIt2 last2);

// Equal
template <class InputIt1, class InputIt2>
bool equal(InputIt1 first1, InputIt1 last1, InputIt2 first2);

template <class InputIt1, class InputIt2>
bool equal(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2);

template <class InputIt1, class InputIt2, class BinaryPred>
bool equal(InputIt1 first1, InputIt1 last1,
InputIt2 first2, InputIt2 last2, BinaryPred p) {
// Need a definition to suppress undefined_internal_type when invoked with lambda
return true;
}

template <class ForwardIt, class T>
void iota(ForwardIt first, ForwardIt last, T value);

} // namespace std

void Positives() {
std::vector<int> I, J;
std::find(I.begin(), I.end(), 0);
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
// CHECK-FIXES: std::ranges::find(I, 0);

std::find(I.cbegin(), I.cend(), 1);
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
// CHECK-FIXES: std::ranges::find(I, 1);

std::find(std::begin(I), std::end(I), 2);
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
// CHECK-FIXES: std::ranges::find(I, 2);

std::find(std::cbegin(I), std::cend(I), 3);
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
// CHECK-FIXES: std::ranges::find(I, 3);

std::find(std::cbegin(I), I.cend(), 4);
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
// CHECK-FIXES: std::ranges::find(I, 4);

std::reverse(I.begin(), I.end());
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
// CHECK-FIXES: std::ranges::reverse(I);

std::includes(I.begin(), I.end(), I.begin(), I.end());
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
// CHECK-FIXES: std::ranges::includes(I, I);

std::includes(I.begin(), I.end(), J.begin(), J.end());
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
// CHECK-FIXES: std::ranges::includes(I, J);

std::is_permutation(I.begin(), I.end(), J.begin(), J.end());
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
// CHECK-FIXES: std::ranges::is_permutation(I, J);

std::equal(I.begin(), I.end(), J.begin(), J.end());
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
// CHECK-FIXES: std::ranges::equal(I, J);

std::equal(I.begin(), I.end(), J.begin(), J.end(), [](int a, int b){ return a == b; });
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
// CHECK-FIXES: std::ranges::equal(I, J, [](int a, int b){ return a == b; });

std::iota(I.begin(), I.end(), 0);
// CHECK-MESSAGES-CPP23: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
// CHECK-FIXES-CPP23: std::ranges::iota(I, 0);

using std::find;
namespace my_std = std;

// Potentially these could be updated to better qualify the replaced function name
find(I.begin(), I.end(), 5);
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
// CHECK-FIXES: std::ranges::find(I, 5);

my_std::find(I.begin(), I.end(), 6);
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
// CHECK-FIXES: std::ranges::find(I, 6);
}

void Reverse(){
std::vector<int> I, J;
std::find(I.rbegin(), I.rend(), 0);
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
// CHECK-FIXES: std::ranges::find(std::views::reverse(I), 0);

std::equal(std::rbegin(I), std::rend(I), J.begin(), J.end());
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
// CHECK-FIXES: std::ranges::equal(std::views::reverse(I), J);

std::equal(I.begin(), I.end(), std::crbegin(J), std::crend(J));
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use a ranges version of this algorithm
// CHECK-FIXES: std::ranges::equal(I, std::views::reverse(J));
}

void Negatives() {
std::vector<int> I, J;
std::find(I.begin(), J.end(), 0);
std::find(I.begin(), I.begin(), 0);
std::find(I.end(), I.begin(), 0);


// Need both ranges for this one
std::is_permutation(I.begin(), I.end(), J.begin());

// We only have one valid match here and the ranges::equal function needs 2 complete ranges
std::equal(I.begin(), I.end(), J.begin());
std::equal(I.begin(), I.end(), J.end(), J.end());
std::equal(std::rbegin(I), std::rend(I), std::rend(J), std::rbegin(J));
std::equal(I.begin(), J.end(), I.begin(), I.end());
}
47 changes: 44 additions & 3 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -236,9 +236,6 @@ C++20 Feature Support
``<expected>`` from libstdc++ to work correctly with Clang.

- User defined constructors are allowed for copy-list-initialization with CTAD.
The example code for deduction guides for std::map in
(`cppreference <https://en.cppreference.com/w/cpp/container/map/deduction_guides>`_)
will now work.
(#GH62925).

C++23 Feature Support
Expand Down Expand Up @@ -272,6 +269,7 @@ C++2c Feature Support

- Implemented `P2809R3: Trivial infinite loops are not Undefined Behavior <https://wg21.link/P2809R3>`_.

- Implemented `P3144R2 Deleting a Pointer to an Incomplete Type Should be Ill-formed <https://wg21.link/P3144R2>`_.

Resolutions to C++ Defect Reports
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down Expand Up @@ -308,6 +306,9 @@ Resolutions to C++ Defect Reports
- Clang now considers ``noexcept(typeid(expr))`` more carefully, instead of always assuming that ``std::bad_typeid`` can be thrown.
(`CWG2191: Incorrect result for noexcept(typeid(v)) <https://cplusplus.github.io/CWG/issues/2191.html>`_).

- Clang now correctly implements lookup for the terminal name of a member-qualified nested-name-specifier.
(`CWG1835: Dependent member lookup before < <https://cplusplus.github.io/CWG/issues/1835.html>`_).

C Language Changes
------------------

Expand Down Expand Up @@ -350,6 +351,9 @@ C23 Feature Support
but C23 added them to ``<float.h>`` in
`WG14 N2848 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2848.pdf>`_.

- Clang now supports `N3017 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3017.htm>`_
``#embed`` - a scannable, tooling-friendly binary resource inclusion mechanism.

Non-comprehensive list of changes in this release
-------------------------------------------------

Expand Down Expand Up @@ -438,6 +442,17 @@ New Compiler Flags
Matches MSVC behaviour by defining ``__STDC__`` to ``1`` when
MSVC compatibility mode is used. It has no effect for C++ code.

- ``-Wc++23-compat`` group was added to help migrating existing codebases
to C++23.

- ``-Wc++2c-compat`` group was added to help migrating existing codebases
to upcoming C++26.

- ``-fdisable-block-signature-string`` instructs clang not to emit the signature
string for blocks. Disabling the string can potentially break existing code
that relies on it. Users should carefully consider this possibiilty when using
the flag.

Deprecated Compiler Flags
-------------------------

Expand Down Expand Up @@ -476,6 +491,10 @@ Modified Compiler Flags
evaluating to ``true`` and an empty body such as ``while(1);``)
are considered infinite, even when the ``-ffinite-loop`` flag is set.

- Diagnostics groups about compatibility with a particular C++ Standard version
now include dianostics about C++26 features that are not present in older
versions.

Removed Compiler Flags
-------------------------

Expand Down Expand Up @@ -538,6 +557,19 @@ Attribute Changes in Clang
size_t count;
};
- The attributes ``sized_by``, ``counted_by_or_null`` and ``sized_by_or_null```
have been added as variants on ``counted_by``, each with slightly different semantics.
``sized_by`` takes a byte size parameter instead of an element count, allowing pointees
with unknown size. The ``counted_by_or_null`` and ``sized_by_or_null`` variants are equivalent
to their base variants, except the pointer can be null regardless of count/size value.
If the pointer is null the size is effectively 0. ``sized_by_or_null`` is needed to properly
annotate allocator functions like ``malloc`` that return a buffer of a given byte size, but can
also return null.

- The ``guarded_by``, ``pt_guarded_by``, ``acquired_after``, ``acquired_before``
attributes now support referencing struct members in C. The arguments are also
now late parsed when ``-fexperimental-late-parse-attributes`` is passed like
for ``counted_by``.

- Introduced new function type attributes ``[[clang::nonblocking]]``, ``[[clang::nonallocating]]``,
``[[clang::blocking]]``, and ``[[clang::allocating]]``, with GNU-style variants as well.
Expand Down Expand Up @@ -654,6 +686,9 @@ Improvements to Clang's diagnostics

- Clang now shows implicit deduction guides when diagnosing overload resolution failure. #GH92393.

- Clang no longer emits a "no previous prototype" warning for Win32 entry points under ``-Wmissing-prototypes``.
Fixes #GH94366.

Improvements to Clang's time-trace
----------------------------------

Expand Down Expand Up @@ -951,6 +986,7 @@ Bug Fixes to C++ Support
- Fix a crash caused by improper use of ``__array_extent``. (#GH80474)
- Fixed several bugs in capturing variables within unevaluated contexts. (#GH63845), (#GH67260), (#GH69307),
(#GH88081), (#GH89496), (#GH90669) and (#GH91633).
- Fixed a crash in constraint instantiation under nested lambdas with dependent parameters.
- Fixed handling of brace ellison when building deduction guides. (#GH64625), (#GH83368).
- Clang now instantiates local constexpr functions eagerly for constant evaluators. (#GH35052), (#GH94849)
- Fixed a failed assertion when attempting to convert an integer representing the difference
Expand All @@ -963,6 +999,10 @@ Bug Fixes to C++ Support
- Fixed an assertion failure about invalid conversion when calling lambda. (#GH96205).
- Fixed a bug where the first operand of binary ``operator&`` would be transformed as if it was the operand
of the address of operator. (#GH97483).
- Fixed an assertion failure about a constant expression which is a known integer but is not
evaluated to an integer. (#GH96670).
- Fixed a bug where references to lambda capture inside a ``noexcept`` specifier were not correctly
instantiated. (#GH95735).

Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down Expand Up @@ -1072,6 +1112,7 @@ RISC-V Support

- ``__attribute__((rvv_vector_bits(N)))`` is now supported for RVV vbool*_t types.
- Profile names in ``-march`` option are now supported.
- Passing empty structs/unions as arguments in C++ is now handled correctly. The behavior is similar to GCC's.

CUDA/HIP Language Changes
^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
68 changes: 68 additions & 0 deletions clang/docs/StandardCPlusPlusModules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1092,6 +1092,74 @@ A high-level overview of support for standards features, including modules, can
be found on the `C++ Feature Status <https://clang.llvm.org/cxx_status.html>`_
page.

Missing VTables for classes attached to modules
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Now the compiler may miss emitting the definition of vtables
for classes attached to modules, if the definition of the class
doesn't contain any key function in that module units
(The key function is the first non-pure virtual function that is
not inline at the point of class definition.)

(Note: technically, the key function is not a thing for modules.
We use the concept here for convinient.)

For example,

.. code-block:: c++

// layer1.cppm
export module foo:layer1;
struct Fruit {
virtual ~Fruit() = default;
virtual void eval() = 0;
};
struct Banana : public Fruit {
Banana() {}
void eval() override;
};

// layer2.cppm
export module foo:layer2;
import :layer1;
export void layer2_fun() {
Banana *b = new Banana();
b->eval();
}
void Banana::eval() {
}
For the above example, we can't find the definition for the vtable of
class ``Banana`` in any object files.

The expected behavior is, for dynamic classes attached to named modules,
the vtable should always be emitted to the module units the class attaches
to.

To workaround the problem, users can add the key function manually in the
corresponding module units. e.g.,

.. code-block:: c++

// layer1.cppm
export module foo:layer1;
struct Fruit {
virtual ~Fruit() = default;
virtual void eval() = 0;
};
struct Banana : public Fruit {
// Hack a key function to hint the compiler to emit the virtual table.
virtual void anchor();

Banana() {}
void eval() override;
};

void Banana::anchor() {}

This is tracked by
`#70585 <https://github.com/llvm/llvm-project/issues/70585>`_.

Including headers after import is not well-supported
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
7 changes: 4 additions & 3 deletions clang/docs/ThreadSafetyAnalysis.rst
Original file line number Diff line number Diff line change
Expand Up @@ -764,10 +764,11 @@ doesn't know that munl.mu == mutex. The SCOPED_CAPABILITY attribute handles
aliasing for MutexLocker, but does so only for that particular pattern.


ACQUIRED_BEFORE(...) and ACQUIRED_AFTER(...) are currently unimplemented.
-------------------------------------------------------------------------
ACQUIRED_BEFORE(...) and ACQUIRED_AFTER(...) support is still experimental.
---------------------------------------------------------------------------

To be fixed in a future update.
ACQUIRED_BEFORE(...) and ACQUIRED_AFTER(...) are currently being developed under
the ``-Wthread-safety-beta`` flag.


.. _mutexheader:
Expand Down
6 changes: 6 additions & 0 deletions clang/docs/UsersManual.rst
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,12 @@ Clang options that don't fit neatly into other categories.
binary compatibility issues on older x86_64 targets, however, so use it with
caution.

.. option:: -fdisable-block-signature-string

Instruct clang not to emit the signature string for blocks. Disabling the
string can potentially break existing code that relies on it. Users should
carefully consider this possibiilty when using the flag.

.. _configuration-files:

Configuration files
Expand Down
Binary file added clang/docs/analyzer/images/analyzer_html.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added clang/docs/analyzer/images/analyzer_xcode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added clang/docs/analyzer/images/scan_build_cmd.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions clang/docs/analyzer/user-docs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,9 @@ Contents:
.. toctree::
:maxdepth: 2

user-docs/Installation
user-docs/CommandLineUsage
user-docs/UsingWithXCode
user-docs/FilingBugs
user-docs/CrossTranslationUnit
user-docs/TaintAnalysisConfiguration
Loading