250 changes: 250 additions & 0 deletions bolt/lib/Passes/ContinuityStats.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
//===- bolt/Passes/ContinuityStats.cpp --------------------------*- 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
//
//===----------------------------------------------------------------------===//
//
// This file implements the continuity stats calculation pass.
//
//===----------------------------------------------------------------------===//

#include "bolt/Passes/ContinuityStats.h"
#include "bolt/Core/BinaryBasicBlock.h"
#include "bolt/Core/BinaryFunction.h"
#include "bolt/Utils/CommandLineOpts.h"
#include "llvm/Support/CommandLine.h"
#include <queue>
#include <unordered_map>
#include <unordered_set>

#define DEBUG_TYPE "bolt-opts"

using namespace llvm;
using namespace bolt;

namespace opts {
extern cl::opt<unsigned> Verbosity;
cl::opt<unsigned> NumFunctionsForContinuityCheck(
"num-functions-for-continuity-check",
cl::desc("number of hottest functions to print aggregated "
"CFG discontinuity stats of."),
cl::init(1000), cl::ZeroOrMore, cl::Hidden, cl::cat(BoltOptCategory));
} // namespace opts

namespace {
using FunctionListType = std::vector<const BinaryFunction *>;
using function_iterator = FunctionListType::iterator;

template <typename T>
void printDistribution(raw_ostream &OS, std::vector<T> &values,
bool Fraction = false) {
if (values.empty())
return;
// Sort values from largest to smallest and print the MAX, TOP 1%, 5%, 10%,
// 20%, 50%, 80%, MIN. If Fraction is true, then values are printed as
// fractions instead of integers.
std::sort(values.begin(), values.end());

auto printLine = [&](std::string Text, double Percent) {
int Rank = int(values.size() * (1.0 - Percent / 100));
if (Percent == 0)
Rank = values.size() - 1;
if (Fraction)
OS << " " << Text << std::string(9 - Text.length(), ' ') << ": "
<< format("%.2lf%%", values[Rank] * 100) << "\n";
else
OS << " " << Text << std::string(9 - Text.length(), ' ') << ": "
<< values[Rank] << "\n";
};

printLine("MAX", 0);
const int percentages[] = {1, 5, 10, 20, 50, 80};
for (size_t i = 0; i < sizeof(percentages) / sizeof(percentages[0]); ++i) {
printLine("TOP " + std::to_string(percentages[i]) + "%", percentages[i]);
}
printLine("MIN", 100);
}

void printCFGContinuityStats(raw_ostream &OS,
iterator_range<function_iterator> &Functions) {
// Given a perfect profile, every positive-execution-count BB should be
// connected to an entry of the function through a positive-execution-count
// directed path in the control flow graph.
std::vector<size_t> NumUnreachables;
std::vector<size_t> SumECUnreachables;
std::vector<double> FractionECUnreachables;

for (auto it = Functions.begin(); it != Functions.end(); ++it) {
const BinaryFunction *Function = *it;
if (Function->size() <= 1)
continue;

// Compute the sum of all BB execution counts (ECs).
size_t NumPosECBBs = 0;
size_t SumAllBBEC = 0;
for (const BinaryBasicBlock &BB : *Function) {
const size_t BBEC = BB.getKnownExecutionCount();
NumPosECBBs += BBEC > 0 ? 1 : 0;
SumAllBBEC += BBEC;
}

// Perform BFS on subgraph of CFG induced by positive weight edges.
// Compute the number of BBs reachable from the entry(s) of the function and
// the sum of their execution counts (ECs).
std::unordered_map<unsigned, const BinaryBasicBlock *> IndexToBB;
std::unordered_set<unsigned> Visited;
std::queue<unsigned> Queue;
for (const BinaryBasicBlock &BB : *Function) {
// Make sure BB.getIndex() is not already in IndexToBB.
assert(IndexToBB.find(BB.getIndex()) == IndexToBB.end());
IndexToBB[BB.getIndex()] = &BB;
if (BB.isEntryPoint() && BB.getKnownExecutionCount() > 0) {
Queue.push(BB.getIndex());
Visited.insert(BB.getIndex());
}
}
while (!Queue.empty()) {
const unsigned BBIndex = Queue.front();
const BinaryBasicBlock *BB = IndexToBB[BBIndex];
Queue.pop();
auto SuccBIIter = BB->branch_info_begin();
for (const BinaryBasicBlock *Succ : BB->successors()) {
const uint64_t Count = SuccBIIter->Count;
if (Count == BinaryBasicBlock::COUNT_NO_PROFILE || Count == 0) {
++SuccBIIter;
continue;
}
if (!Visited.insert(Succ->getIndex()).second) {
++SuccBIIter;
continue;
}
Queue.push(Succ->getIndex());
++SuccBIIter;
}
}

const size_t NumReachableBBs = Visited.size();

// Loop through Visited, and sum the corresponding BBs' execution counts
// (ECs).
size_t SumReachableBBEC = 0;
for (const unsigned BBIndex : Visited) {
const BinaryBasicBlock *BB = IndexToBB[BBIndex];
SumReachableBBEC += BB->getKnownExecutionCount();
}

const size_t NumPosECBBsUnreachableFromEntry =
NumPosECBBs - NumReachableBBs;
const size_t SumUnreachableBBEC = SumAllBBEC - SumReachableBBEC;
const double FractionECUnreachable =
(double)SumUnreachableBBEC / SumAllBBEC;

if (opts::Verbosity >= 2 && FractionECUnreachable >= 0.05) {
OS << "Non-trivial CFG discontinuity observed in function "
<< Function->getPrintName() << "\n";
LLVM_DEBUG(Function->dump());
}

NumUnreachables.push_back(NumPosECBBsUnreachableFromEntry);
SumECUnreachables.push_back(SumUnreachableBBEC);
FractionECUnreachables.push_back(FractionECUnreachable);
}

if (FractionECUnreachables.empty())
return;

std::sort(FractionECUnreachables.begin(), FractionECUnreachables.end());
const int Rank = int(FractionECUnreachables.size() * 0.95);
OS << format("top 5%% function CFG discontinuity is %.2lf%%\n",
FractionECUnreachables[Rank] * 100);

if (opts::Verbosity >= 1) {
OS << "abbreviations: EC = execution count, POS BBs = positive EC BBs\n"
<< "distribution of NUM(unreachable POS BBs) among all focal "
"functions\n";
printDistribution(OS, NumUnreachables);

OS << "distribution of SUM_EC(unreachable POS BBs) among all focal "
"functions\n";
printDistribution(OS, SumECUnreachables);

OS << "distribution of [(SUM_EC(unreachable POS BBs) / SUM_EC(all "
"POS BBs))] among all focal functions\n";
printDistribution(OS, FractionECUnreachables, /*Fraction=*/true);
}
}

void printAll(BinaryContext &BC, FunctionListType &ValidFunctions,
size_t NumTopFunctions) {
// Sort the list of functions by execution counts (reverse).
llvm::sort(ValidFunctions,
[&](const BinaryFunction *A, const BinaryFunction *B) {
return A->getKnownExecutionCount() > B->getKnownExecutionCount();
});

const size_t RealNumTopFunctions =
std::min(NumTopFunctions, ValidFunctions.size());

iterator_range<function_iterator> Functions(
ValidFunctions.begin(), ValidFunctions.begin() + RealNumTopFunctions);

BC.outs() << format("BOLT-INFO: among the hottest %zu functions ",
RealNumTopFunctions);
printCFGContinuityStats(BC.outs(), Functions);

// Print more detailed bucketed stats if requested.
if (opts::Verbosity >= 1 && RealNumTopFunctions >= 5) {
const size_t PerBucketSize = RealNumTopFunctions / 5;
BC.outs() << format(
"Detailed stats for 5 buckets, each with %zu functions:\n",
PerBucketSize);

// For each bucket, print the CFG continuity stats of the functions in the
// bucket.
for (size_t BucketIndex = 0; BucketIndex < 5; ++BucketIndex) {
const size_t StartIndex = BucketIndex * PerBucketSize;
const size_t EndIndex = StartIndex + PerBucketSize;
iterator_range<function_iterator> Functions(
ValidFunctions.begin() + StartIndex,
ValidFunctions.begin() + EndIndex);
const size_t MaxFunctionExecutionCount =
ValidFunctions[StartIndex]->getKnownExecutionCount();
const size_t MinFunctionExecutionCount =
ValidFunctions[EndIndex - 1]->getKnownExecutionCount();
BC.outs() << format("----------------\n| Bucket %zu: "
"|\n----------------\n",
BucketIndex + 1)
<< format(
"execution counts of the %zu functions in the bucket: "
"%zu-%zu\n",
EndIndex - StartIndex, MinFunctionExecutionCount,
MaxFunctionExecutionCount);
printCFGContinuityStats(BC.outs(), Functions);
}
}
}
} // namespace

bool PrintContinuityStats::shouldOptimize(const BinaryFunction &BF) const {
if (BF.empty() || !BF.hasValidProfile())
return false;

return BinaryFunctionPass::shouldOptimize(BF);
}

Error PrintContinuityStats::runOnFunctions(BinaryContext &BC) {
// Create a list of functions with valid profiles.
FunctionListType ValidFunctions;
for (const auto &BFI : BC.getBinaryFunctions()) {
const BinaryFunction *Function = &BFI.second;
if (PrintContinuityStats::shouldOptimize(*Function))
ValidFunctions.push_back(Function);
}
if (ValidFunctions.empty() || opts::NumFunctionsForContinuityCheck == 0)
return Error::success();

printAll(BC, ValidFunctions, opts::NumFunctionsForContinuityCheck);
return Error::success();
}
6 changes: 2 additions & 4 deletions bolt/lib/Passes/Instrumentation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,8 @@ static bool hasAArch64ExclusiveMemop(
BinaryBasicBlock *BB = BBQueue.front().first;
bool IsLoad = BBQueue.front().second;
BBQueue.pop();
if (Visited.find(BB) != Visited.end())
if (!Visited.insert(BB).second)
continue;
Visited.insert(BB);

for (const MCInst &Inst : *BB) {
// Two loads one after another - skip whole function
Expand All @@ -126,8 +125,7 @@ static bool hasAArch64ExclusiveMemop(
if (BC.MIB->isAArch64ExclusiveLoad(Inst))
IsLoad = true;

if (IsLoad && BBToSkip.find(BB) == BBToSkip.end()) {
BBToSkip.insert(BB);
if (IsLoad && BBToSkip.insert(BB).second) {
if (opts::Verbosity >= 2) {
outs() << "BOLT-INSTRUMENTER: skip BB " << BB->getName()
<< " due to exclusive instruction in function "
Expand Down
22 changes: 10 additions & 12 deletions bolt/lib/Profile/BoltAddressTranslation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,8 @@ void BoltAddressTranslation::write(const BinaryContext &BC, raw_ostream &OS) {

// Output addresses are delta-encoded
uint64_t PrevAddress = 0;
writeMaps</*Cold=*/false>(Maps, PrevAddress, OS);
writeMaps</*Cold=*/true>(Maps, PrevAddress, OS);
writeMaps</*Cold=*/false>(PrevAddress, OS);
writeMaps</*Cold=*/true>(PrevAddress, OS);

BC.outs() << "BOLT-INFO: Wrote " << Maps.size() << " BAT maps\n";
BC.outs() << "BOLT-INFO: Wrote " << FuncHashes.getNumFunctions()
Expand Down Expand Up @@ -182,8 +182,7 @@ size_t BoltAddressTranslation::getNumEqualOffsets(const MapTy &Map,
}

template <bool Cold>
void BoltAddressTranslation::writeMaps(std::map<uint64_t, MapTy> &Maps,
uint64_t &PrevAddress, raw_ostream &OS) {
void BoltAddressTranslation::writeMaps(uint64_t &PrevAddress, raw_ostream &OS) {
const uint32_t NumFuncs =
llvm::count_if(llvm::make_first_range(Maps), [&](const uint64_t Address) {
return Cold == ColdPartSource.count(Address);
Expand Down Expand Up @@ -213,16 +212,17 @@ void BoltAddressTranslation::writeMaps(std::map<uint64_t, MapTy> &Maps,
: 0;
uint32_t Skew = 0;
if (Cold) {
auto HotEntryIt = Maps.find(ColdPartSource[Address]);
assert(HotEntryIt != Maps.end());
size_t HotIndex = std::distance(Maps.begin(), HotEntryIt);
auto HotEntryIt = llvm::lower_bound(HotFuncs, ColdPartSource[Address]);
assert(HotEntryIt != HotFuncs.end());
size_t HotIndex = std::distance(HotFuncs.begin(), HotEntryIt);
encodeULEB128(HotIndex - PrevIndex, OS);
PrevIndex = HotIndex;
// Skew of all input offsets for cold fragments is simply the first input
// offset.
Skew = Map.begin()->second >> 1;
encodeULEB128(Skew, OS);
} else {
HotFuncs.push_back(Address);
// Function hash
size_t BFHash = getBFHash(HotInputAddress);
LLVM_DEBUG(dbgs() << "Hash: " << formatv("{0:x}\n", BFHash));
Expand Down Expand Up @@ -311,17 +311,15 @@ std::error_code BoltAddressTranslation::parse(raw_ostream &OS, StringRef Buf) {
return make_error_code(llvm::errc::io_error);

Error Err(Error::success());
std::vector<uint64_t> HotFuncs;
uint64_t PrevAddress = 0;
parseMaps</*Cold=*/false>(HotFuncs, PrevAddress, DE, Offset, Err);
parseMaps</*Cold=*/true>(HotFuncs, PrevAddress, DE, Offset, Err);
parseMaps</*Cold=*/false>(PrevAddress, DE, Offset, Err);
parseMaps</*Cold=*/true>(PrevAddress, DE, Offset, Err);
OS << "BOLT-INFO: Parsed " << Maps.size() << " BAT entries\n";
return errorToErrorCode(std::move(Err));
}

template <bool Cold>
void BoltAddressTranslation::parseMaps(std::vector<uint64_t> &HotFuncs,
uint64_t &PrevAddress, DataExtractor &DE,
void BoltAddressTranslation::parseMaps(uint64_t &PrevAddress, DataExtractor &DE,
uint64_t &Offset, Error &Err) {
const uint32_t NumFunctions = DE.getULEB128(&Offset, &Err);
LLVM_DEBUG(dbgs() << "Parsing " << NumFunctions << (Cold ? " cold" : "")
Expand Down
6 changes: 1 addition & 5 deletions bolt/lib/Profile/YAMLProfileReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -643,11 +643,7 @@ size_t YAMLProfileReader::matchWithNameSimilarity(BinaryContext &BC) {
// equal number of blocks.
if (NamespaceToProfiledBFSizesIt->second.count(BF->size()) == 0)
continue;
auto NamespaceToBFsIt = NamespaceToBFs.find(Namespace);
if (NamespaceToBFsIt == NamespaceToBFs.end())
NamespaceToBFs[Namespace] = {BF};
else
NamespaceToBFsIt->second.push_back(BF);
NamespaceToBFs[Namespace].push_back(BF);
}

// Iterates through all profiled functions and binary functions belonging to
Expand Down
3 changes: 3 additions & 0 deletions bolt/lib/Rewrite/BinaryPassManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "bolt/Passes/AllocCombiner.h"
#include "bolt/Passes/AsmDump.h"
#include "bolt/Passes/CMOVConversion.h"
#include "bolt/Passes/ContinuityStats.h"
#include "bolt/Passes/FixRISCVCallsPass.h"
#include "bolt/Passes/FixRelaxationPass.h"
#include "bolt/Passes/FrameOptimizer.h"
Expand Down Expand Up @@ -373,6 +374,8 @@ Error BinaryFunctionPassManager::runAllPasses(BinaryContext &BC) {
if (opts::PrintProfileStats)
Manager.registerPass(std::make_unique<PrintProfileStats>(NeverPrint));

Manager.registerPass(std::make_unique<PrintContinuityStats>(NeverPrint));

Manager.registerPass(std::make_unique<ValidateInternalCalls>(NeverPrint));

Manager.registerPass(std::make_unique<ValidateMemRefs>(NeverPrint));
Expand Down
49 changes: 49 additions & 0 deletions bolt/test/AArch64/adr-relaxation.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
## Check that llvm-bolt will not unnecessarily relax ADR instruction.

# RUN: llvm-mc -filetype=obj -triple aarch64-unknown-unknown %s -o %t.o
# RUN: %clang %cflags %t.o -o %t.exe -Wl,-q -static
# RUN: llvm-bolt %t.exe -o %t.bolt --split-functions --split-strategy=random2
# RUN: llvm-objdump -d --disassemble-symbols=_start %t.bolt | FileCheck %s
# RUN: llvm-objdump -d --disassemble-symbols=foo.cold.0 %t.bolt \
# RUN: | FileCheck --check-prefix=CHECK-FOO %s

## ADR below references its containing function that is split. But ADR is always
## in the main fragment, thus there is no need to relax it.
.text
.globl _start
.type _start, %function
_start:
# CHECK: <_start>:
.cfi_startproc
adr x1, _start
# CHECK-NOT: adrp
# CHECK: adr
cmp x1, x11
b.hi .L1
mov x0, #0x0
.L1:
ret x30
.cfi_endproc
.size _start, .-_start


## In foo, ADR is in the split fragment but references the main one. Thus, it
## needs to be relaxed into ADRP + ADD.
.globl foo
.type foo, %function
foo:
.cfi_startproc
cmp x1, x11
b.hi .L2
mov x0, #0x0
.L2:
# CHECK-FOO: <foo.cold.0>:
adr x1, foo
# CHECK-FOO: adrp
# CHECK-FOO-NEXT: add
ret x30
.cfi_endproc
.size foo, .-foo

## Force relocation mode.
.reloc 0, R_AARCH64_NONE
4 changes: 4 additions & 0 deletions bolt/test/X86/cfg-discontinuity-reporting.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## Check profile discontinuity reporting
RUN: yaml2obj %p/Inputs/blarge_new.yaml &> %t.exe
RUN: llvm-bolt %t.exe -o %t.out --pa -p %p/Inputs/blarge_new.preagg.txt | FileCheck %s
CHECK: among the hottest 5 functions top 5% function CFG discontinuity is 100.00%
4 changes: 2 additions & 2 deletions bolt/tools/driver/llvm-bolt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,9 +202,9 @@ int main(int argc, char **argv) {

ToolName = argv[0];

if (llvm::sys::path::filename(ToolName) == "perf2bolt")
if (llvm::sys::path::filename(ToolName).starts_with("perf2bolt"))
perf2boltMode(argc, argv);
else if (llvm::sys::path::filename(ToolName) == "llvm-boltdiff")
else if (llvm::sys::path::filename(ToolName).starts_with("llvm-boltdiff"))
boltDiffMode(argc, argv);
else
boltMode(argc, argv);
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-apply-replacements/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ set(LLVM_LINK_COMPONENTS
Support
)

add_clang_library(clangApplyReplacements
add_clang_library(clangApplyReplacements STATIC
lib/Tooling/ApplyReplacements.cpp

DEPENDS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,8 @@ groupReplacements(const TUReplacements &TUs, const TUDiagnostics &TUDs,

if (auto Entry = SM.getFileManager().getOptionalFileRef(Path)) {
if (SourceTU) {
auto &Replaces = DiagReplacements[*Entry];
auto It = Replaces.find(R);
if (It == Replaces.end())
Replaces.emplace(R, SourceTU);
else if (It->second != SourceTU)
auto [It, Inserted] = DiagReplacements[*Entry].try_emplace(R, SourceTU);
if (!Inserted && It->second != SourceTU)
// This replacement is a duplicate of one suggested by another TU.
return;
}
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-change-namespace/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set(LLVM_LINK_COMPONENTS
Support
)

add_clang_library(clangChangeNamespace
add_clang_library(clangChangeNamespace STATIC
ChangeNamespace.cpp

DEPENDS
Expand Down
3 changes: 1 addition & 2 deletions clang-tools-extra/clang-change-namespace/ChangeNamespace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -606,9 +606,8 @@ void ChangeNamespaceTool::run(
Result.Nodes.getNodeAs<DeclRefExpr>("func_ref")) {
// If this reference has been processed as a function call, we do not
// process it again.
if (ProcessedFuncRefs.count(FuncRef))
if (!ProcessedFuncRefs.insert(FuncRef).second)
return;
ProcessedFuncRefs.insert(FuncRef);
const auto *Func = Result.Nodes.getNodeAs<FunctionDecl>("func_decl");
assert(Func);
const auto *Context = Result.Nodes.getNodeAs<Decl>("dc");
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-doc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ set(LLVM_LINK_COMPONENTS
FrontendOpenMP
)

add_clang_library(clangDoc
add_clang_library(clangDoc STATIC
BitcodeReader.cpp
BitcodeWriter.cpp
ClangDoc.cpp
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-include-fixer/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ set(LLVM_LINK_COMPONENTS
support
)

add_clang_library(clangIncludeFixer
add_clang_library(clangIncludeFixer STATIC
IncludeFixer.cpp
IncludeFixerContext.cpp
InMemorySymbolIndex.cpp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set(LLVM_LINK_COMPONENTS
FrontendOpenMP
)

add_clang_library(findAllSymbols
add_clang_library(findAllSymbols STATIC
FindAllSymbols.cpp
FindAllSymbolsAction.cpp
FindAllMacros.cpp
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
add_clang_library(clangIncludeFixerPlugin
add_clang_library(clangIncludeFixerPlugin STATIC
IncludeFixerPlugin.cpp

LINK_LIBS
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-move/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set(LLVM_LINK_COMPONENTS
FrontendOpenMP
)

add_clang_library(clangMove
add_clang_library(clangMove STATIC
Move.cpp
HelperDeclRefGraph.cpp

Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-query/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ set(LLVM_LINK_COMPONENTS
FrontendOpenMP
)

add_clang_library(clangQuery
add_clang_library(clangQuery STATIC
Query.cpp
QueryParser.cpp

Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-reorder-fields/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set(LLVM_LINK_COMPONENTS
support
)

add_clang_library(clangReorderFields
add_clang_library(clangReorderFields STATIC
ReorderFieldsAction.cpp

DEPENDS
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-tidy/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ configure_file(
${CMAKE_CURRENT_BINARY_DIR}/clang-tidy-config.h)
include_directories(BEFORE ${CMAKE_CURRENT_BINARY_DIR})

add_clang_library(clangTidy
add_clang_library(clangTidy STATIC
ClangTidy.cpp
ClangTidyCheck.cpp
ClangTidyModule.cpp
Expand Down
12 changes: 12 additions & 0 deletions clang-tools-extra/clang-tidy/ClangTidy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,18 @@ getAllChecksAndOptions(bool AllowEnablingAnalyzerAlphaCheckers) {
Buffer.append(AnalyzerCheck);
Result.Names.insert(Buffer);
}
for (std::string OptionName : {
#define GET_CHECKER_OPTIONS
#define CHECKER_OPTION(TYPE, CHECKER, OPTION_NAME, DESCRIPTION, DEFAULT, \
RELEASE, HIDDEN) \
Twine(AnalyzerCheckNamePrefix).concat(CHECKER ":" OPTION_NAME).str(),

#include "clang/StaticAnalyzer/Checkers/Checkers.inc"
#undef CHECKER_OPTION
#undef GET_CHECKER_OPTIONS
}) {
Result.Options.insert(OptionName);
}
#endif // CLANG_TIDY_ENABLE_STATIC_ANALYZER

Context.setOptionsCollector(&Result.Options);
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-tidy/abseil/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set(LLVM_LINK_COMPONENTS
FrontendOpenMP
)

add_clang_library(clangTidyAbseilModule
add_clang_library(clangTidyAbseilModule STATIC
AbseilTidyModule.cpp
CleanupCtadCheck.cpp
DurationAdditionCheck.cpp
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-tidy/altera/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set(LLVM_LINK_COMPONENTS
support
)

add_clang_library(clangTidyAlteraModule
add_clang_library(clangTidyAlteraModule STATIC
AlteraTidyModule.cpp
IdDependentBackwardBranchCheck.cpp
KernelNameRestrictionCheck.cpp
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-tidy/android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set(LLVM_LINK_COMPONENTS
FrontendOpenMP
)

add_clang_library(clangTidyAndroidModule
add_clang_library(clangTidyAndroidModule STATIC
AndroidTidyModule.cpp
CloexecAccept4Check.cpp
CloexecAcceptCheck.cpp
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-tidy/boost/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set(LLVM_LINK_COMPONENTS
FrontendOpenMP
)

add_clang_library(clangTidyBoostModule
add_clang_library(clangTidyBoostModule STATIC
BoostTidyModule.cpp
UseRangesCheck.cpp
UseToStringCheck.cpp
Expand Down
18 changes: 9 additions & 9 deletions clang-tools-extra/clang-tidy/boost/UseRangesCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ utils::UseRangesCheck::ReplacerMap UseRangesCheck::getReplacerMap() const {
ReplacerMap Results;
static const Signature SingleSig = {{0}};
static const Signature TwoSig = {{0}, {2}};
static const auto AddFrom =
const auto AddFrom =
[&Results](llvm::IntrusiveRefCntPtr<UseRangesCheck::Replacer> Replacer,
std::initializer_list<StringRef> Names, StringRef Prefix) {
llvm::SmallString<64> Buffer;
Expand All @@ -214,17 +214,17 @@ utils::UseRangesCheck::ReplacerMap UseRangesCheck::getReplacerMap() const {
}
};

static const auto AddFromStd =
[](llvm::IntrusiveRefCntPtr<UseRangesCheck::Replacer> Replacer,
std::initializer_list<StringRef> Names) {
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) {
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() ? "" : "::"),
Expand Down
46 changes: 46 additions & 0 deletions clang-tools-extra/clang-tidy/bugprone/BitwisePointerCastCheck.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//===--- BitwisePointerCastCheck.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 "BitwisePointerCastCheck.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"

using namespace clang::ast_matchers;

namespace clang::tidy::bugprone {

void BitwisePointerCastCheck::registerMatchers(MatchFinder *Finder) {
if (getLangOpts().CPlusPlus20) {
auto IsPointerType = refersToType(qualType(isAnyPointer()));
Finder->addMatcher(callExpr(hasDeclaration(functionDecl(allOf(
hasName("::std::bit_cast"),
hasTemplateArgument(0, IsPointerType),
hasTemplateArgument(1, IsPointerType)))))
.bind("bit_cast"),
this);
}

auto IsDoublePointerType =
hasType(qualType(pointsTo(qualType(isAnyPointer()))));
Finder->addMatcher(callExpr(hasArgument(0, IsDoublePointerType),
hasArgument(1, IsDoublePointerType),
hasDeclaration(functionDecl(hasName("::memcpy"))))
.bind("memcpy"),
this);
}

void BitwisePointerCastCheck::check(const MatchFinder::MatchResult &Result) {
if (const auto *Call = Result.Nodes.getNodeAs<CallExpr>("bit_cast"))
diag(Call->getBeginLoc(),
"do not use 'std::bit_cast' to cast between pointers")
<< Call->getSourceRange();
else if (const auto *Call = Result.Nodes.getNodeAs<CallExpr>("memcpy"))
diag(Call->getBeginLoc(), "do not use 'memcpy' to cast between pointers")
<< Call->getSourceRange();
}

} // namespace clang::tidy::bugprone
34 changes: 34 additions & 0 deletions clang-tools-extra/clang-tidy/bugprone/BitwisePointerCastCheck.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//===--- BitwisePointerCastCheck.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_BUGPRONE_BITWISEPOINTERCASTCHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_BITWISEPOINTERCASTCHECK_H

#include "../ClangTidyCheck.h"

namespace clang::tidy::bugprone {

/// Warns about code that tries to cast between pointers by means of
/// ``std::bit_cast`` or ``memcpy``.
///
/// For the user-facing documentation see:
/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/bitwise-pointer-cast.html
class BitwisePointerCastCheck : public ClangTidyCheck {
public:
BitwisePointerCastCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context) {}
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
return LangOpts.CPlusPlus;
}
};

} // namespace clang::tidy::bugprone

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_BITWISEPOINTERCASTCHECK_H
6 changes: 6 additions & 0 deletions clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "AssertSideEffectCheck.h"
#include "AssignmentInIfConditionCheck.h"
#include "BadSignalToKillThreadCheck.h"
#include "BitwisePointerCastCheck.h"
#include "BoolPointerImplicitConversionCheck.h"
#include "BranchCloneCheck.h"
#include "CastingThroughVoidCheck.h"
Expand Down Expand Up @@ -78,6 +79,7 @@
#include "SuspiciousStringviewDataUsageCheck.h"
#include "SwappedArgumentsCheck.h"
#include "SwitchMissingDefaultCaseCheck.h"
#include "TaggedUnionMemberCountCheck.h"
#include "TerminatingContinueCheck.h"
#include "ThrowKeywordMissingCheck.h"
#include "TooSmallLoopVariableCheck.h"
Expand Down Expand Up @@ -108,6 +110,8 @@ class BugproneModule : public ClangTidyModule {
"bugprone-assignment-in-if-condition");
CheckFactories.registerCheck<BadSignalToKillThreadCheck>(
"bugprone-bad-signal-to-kill-thread");
CheckFactories.registerCheck<BitwisePointerCastCheck>(
"bugprone-bitwise-pointer-cast");
CheckFactories.registerCheck<BoolPointerImplicitConversionCheck>(
"bugprone-bool-pointer-implicit-conversion");
CheckFactories.registerCheck<BranchCloneCheck>("bugprone-branch-clone");
Expand Down Expand Up @@ -229,6 +233,8 @@ class BugproneModule : public ClangTidyModule {
"bugprone-suspicious-stringview-data-usage");
CheckFactories.registerCheck<SwappedArgumentsCheck>(
"bugprone-swapped-arguments");
CheckFactories.registerCheck<TaggedUnionMemberCountCheck>(
"bugprone-tagged-union-member-count");
CheckFactories.registerCheck<TerminatingContinueCheck>(
"bugprone-terminating-continue");
CheckFactories.registerCheck<ThrowKeywordMissingCheck>(
Expand Down
4 changes: 3 additions & 1 deletion clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ set(LLVM_LINK_COMPONENTS
FrontendOpenMP
)

add_clang_library(clangTidyBugproneModule
add_clang_library(clangTidyBugproneModule STATIC
ArgumentCommentCheck.cpp
AssertSideEffectCheck.cpp
AssignmentInIfConditionCheck.cpp
BadSignalToKillThreadCheck.cpp
BitwisePointerCastCheck.cpp
BoolPointerImplicitConversionCheck.cpp
BranchCloneCheck.cpp
BugproneTidyModule.cpp
Expand Down Expand Up @@ -73,6 +74,7 @@ add_clang_library(clangTidyBugproneModule
SuspiciousSemicolonCheck.cpp
SuspiciousStringCompareCheck.cpp
SwappedArgumentsCheck.cpp
TaggedUnionMemberCountCheck.cpp
TerminatingContinueCheck.cpp
ThrowKeywordMissingCheck.cpp
TooSmallLoopVariableCheck.cpp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,13 @@ void ForwardDeclarationNamespaceCheck::onEndOfTranslationUnit() {
}
// Check if a definition in another namespace exists.
const auto DeclName = CurDecl->getName();
if (!DeclNameToDefinitions.contains(DeclName)) {
auto It = DeclNameToDefinitions.find(DeclName);
if (It == DeclNameToDefinitions.end()) {
continue; // No definition in this translation unit, we can skip it.
}
// Make a warning for each definition with the same name (in other
// namespaces).
const auto &Definitions = DeclNameToDefinitions[DeclName];
const auto &Definitions = It->second;
for (const auto *Def : Definitions) {
diag(CurDecl->getLocation(),
"no definition found for %0, but a definition with "
Expand Down
199 changes: 199 additions & 0 deletions clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
//===--- TaggedUnionMemberCountCheck.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 "TaggedUnionMemberCountCheck.h"
#include "../utils/OptionsUtils.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallSet.h"

using namespace clang::ast_matchers;

namespace clang::tidy::bugprone {

static constexpr llvm::StringLiteral StrictModeOptionName = "StrictMode";
static constexpr llvm::StringLiteral EnableCountingEnumHeuristicOptionName =
"EnableCountingEnumHeuristic";
static constexpr llvm::StringLiteral CountingEnumPrefixesOptionName =
"CountingEnumPrefixes";
static constexpr llvm::StringLiteral CountingEnumSuffixesOptionName =
"CountingEnumSuffixes";

static constexpr bool StrictModeOptionDefaultValue = false;
static constexpr bool EnableCountingEnumHeuristicOptionDefaultValue = true;
static constexpr llvm::StringLiteral CountingEnumPrefixesOptionDefaultValue =
"";
static constexpr llvm::StringLiteral CountingEnumSuffixesOptionDefaultValue =
"count";

static constexpr llvm::StringLiteral RootMatchBindName = "root";
static constexpr llvm::StringLiteral UnionMatchBindName = "union";
static constexpr llvm::StringLiteral TagMatchBindName = "tags";

namespace {

AST_MATCHER_P2(RecordDecl, fieldCountOfKindIsOne,
ast_matchers::internal::Matcher<FieldDecl>, InnerMatcher,
StringRef, BindName) {
// BoundNodesTreeBuilder resets itself when a match occurs.
// So to avoid losing previously saved binds, a temporary instance
// is used for matching.
//
// For precedence, see commit: 5b07de1a5faf4a22ae6fd982b877c5e7e3a76559
clang::ast_matchers::internal::BoundNodesTreeBuilder TempBuilder;

const FieldDecl *FirstMatch = nullptr;
for (const FieldDecl *Field : Node.fields()) {
if (InnerMatcher.matches(*Field, Finder, &TempBuilder)) {
if (FirstMatch) {
return false;
} else {
FirstMatch = Field;
}
}
}

if (FirstMatch) {
Builder->setBinding(BindName, clang::DynTypedNode::create(*FirstMatch));
return true;
}
return false;
}

} // namespace

TaggedUnionMemberCountCheck::TaggedUnionMemberCountCheck(
StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
StrictMode(
Options.get(StrictModeOptionName, StrictModeOptionDefaultValue)),
EnableCountingEnumHeuristic(
Options.get(EnableCountingEnumHeuristicOptionName,
EnableCountingEnumHeuristicOptionDefaultValue)),
CountingEnumPrefixes(utils::options::parseStringList(
Options.get(CountingEnumPrefixesOptionName,
CountingEnumPrefixesOptionDefaultValue))),
CountingEnumSuffixes(utils::options::parseStringList(
Options.get(CountingEnumSuffixesOptionName,
CountingEnumSuffixesOptionDefaultValue))) {
if (!EnableCountingEnumHeuristic) {
if (Options.get(CountingEnumPrefixesOptionName))
configurationDiag("%0: Counting enum heuristic is disabled but "
"%1 is set")
<< Name << CountingEnumPrefixesOptionName;
if (Options.get(CountingEnumSuffixesOptionName))
configurationDiag("%0: Counting enum heuristic is disabled but "
"%1 is set")
<< Name << CountingEnumSuffixesOptionName;
}
}

void TaggedUnionMemberCountCheck::storeOptions(
ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, StrictModeOptionName, StrictMode);
Options.store(Opts, EnableCountingEnumHeuristicOptionName,
EnableCountingEnumHeuristic);
Options.store(Opts, CountingEnumPrefixesOptionName,
utils::options::serializeStringList(CountingEnumPrefixes));
Options.store(Opts, CountingEnumSuffixesOptionName,
utils::options::serializeStringList(CountingEnumSuffixes));
}

void TaggedUnionMemberCountCheck::registerMatchers(MatchFinder *Finder) {

auto UnionField = fieldDecl(hasType(qualType(
hasCanonicalType(recordType(hasDeclaration(recordDecl(isUnion())))))));

auto EnumField = fieldDecl(hasType(
qualType(hasCanonicalType(enumType(hasDeclaration(enumDecl()))))));

auto hasOneUnionField = fieldCountOfKindIsOne(UnionField, UnionMatchBindName);
auto hasOneEnumField = fieldCountOfKindIsOne(EnumField, TagMatchBindName);

Finder->addMatcher(recordDecl(anyOf(isStruct(), isClass()), hasOneUnionField,
hasOneEnumField, unless(isImplicit()))
.bind(RootMatchBindName),
this);
}

bool TaggedUnionMemberCountCheck::isCountingEnumLikeName(StringRef Name) const {
if (llvm::any_of(CountingEnumPrefixes, [Name](StringRef Prefix) -> bool {
return Name.starts_with_insensitive(Prefix);
}))
return true;
if (llvm::any_of(CountingEnumSuffixes, [Name](StringRef Suffix) -> bool {
return Name.ends_with_insensitive(Suffix);
}))
return true;
return false;
}

std::pair<const std::size_t, const EnumConstantDecl *>
TaggedUnionMemberCountCheck::getNumberOfEnumValues(const EnumDecl *ED) {
llvm::SmallSet<llvm::APSInt, 16> EnumValues;

const EnumConstantDecl *LastEnumConstant = nullptr;
for (const EnumConstantDecl *Enumerator : ED->enumerators()) {
EnumValues.insert(Enumerator->getInitVal());
LastEnumConstant = Enumerator;
}

if (EnableCountingEnumHeuristic && LastEnumConstant &&
isCountingEnumLikeName(LastEnumConstant->getName()) &&
(LastEnumConstant->getInitVal() == (EnumValues.size() - 1))) {
return {EnumValues.size() - 1, LastEnumConstant};
}

return {EnumValues.size(), nullptr};
}

void TaggedUnionMemberCountCheck::check(
const MatchFinder::MatchResult &Result) {
const auto *Root = Result.Nodes.getNodeAs<RecordDecl>(RootMatchBindName);
const auto *UnionField =
Result.Nodes.getNodeAs<FieldDecl>(UnionMatchBindName);
const auto *TagField = Result.Nodes.getNodeAs<FieldDecl>(TagMatchBindName);

assert(Root && "Root is missing!");
assert(UnionField && "UnionField is missing!");
assert(TagField && "TagField is missing!");
if (!Root || !UnionField || !TagField)
return;

const auto *UnionDef =
UnionField->getType().getCanonicalType().getTypePtr()->getAsRecordDecl();
const auto *EnumDef = llvm::dyn_cast<EnumDecl>(
TagField->getType().getCanonicalType().getTypePtr()->getAsTagDecl());

assert(UnionDef && "UnionDef is missing!");
assert(EnumDef && "EnumDef is missing!");
if (!UnionDef || !EnumDef)
return;

const std::size_t UnionMemberCount = llvm::range_size(UnionDef->fields());
auto [TagCount, CountingEnumConstantDecl] = getNumberOfEnumValues(EnumDef);

if (UnionMemberCount > TagCount) {
diag(Root->getLocation(),
"tagged union has more data members (%0) than tags (%1)!")
<< UnionMemberCount << TagCount;
} else if (StrictMode && UnionMemberCount < TagCount) {
diag(Root->getLocation(),
"tagged union has fewer data members (%0) than tags (%1)!")
<< UnionMemberCount << TagCount;
}

if (CountingEnumConstantDecl) {
diag(CountingEnumConstantDecl->getLocation(),
"assuming that this constant is just an auxiliary value and not "
"used for indicating a valid union data member",
DiagnosticIDs::Note);
}
}

} // namespace clang::tidy::bugprone
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//===--- TaggedUnionMemberCountCheck.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_BUGPRONE_TAGGEDUNIONMEMBERCOUNTCHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_TAGGEDUNIONMEMBERCOUNTCHECK_H

#include "../ClangTidyCheck.h"

namespace clang::tidy::bugprone {

/// Gives warnings for tagged unions, where the number of tags is
/// different from the number of data members inside the union.
///
/// For the user-facing documentation see:
/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/tagged-union-member-count.html
class TaggedUnionMemberCountCheck : public ClangTidyCheck {
public:
TaggedUnionMemberCountCheck(StringRef Name, ClangTidyContext *Context);
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;

private:
const bool StrictMode;
const bool EnableCountingEnumHeuristic;
const std::vector<StringRef> CountingEnumPrefixes;
const std::vector<StringRef> CountingEnumSuffixes;

std::pair<const std::size_t, const EnumConstantDecl *>
getNumberOfEnumValues(const EnumDecl *ED);
bool isCountingEnumLikeName(StringRef Name) const;
};

} // namespace clang::tidy::bugprone

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_TAGGEDUNIONMEMBERCOUNTCHECK_H
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-tidy/cert/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set(LLVM_LINK_COMPONENTS
FrontendOpenMP
)

add_clang_library(clangTidyCERTModule
add_clang_library(clangTidyCERTModule STATIC
CERTTidyModule.cpp
CommandProcessorCheck.cpp
DefaultOperatorNewAlignmentCheck.cpp
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-tidy/concurrency/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set(LLVM_LINK_COMPONENTS
Support
)

add_clang_library(clangTidyConcurrencyModule
add_clang_library(clangTidyConcurrencyModule STATIC
ConcurrencyTidyModule.cpp
MtUnsafeCheck.cpp
ThreadCanceltypeAsynchronousCheck.cpp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set(LLVM_LINK_COMPONENTS
Support
)

add_clang_library(clangTidyCppCoreGuidelinesModule
add_clang_library(clangTidyCppCoreGuidelinesModule STATIC
AvoidCapturingLambdaCoroutinesCheck.cpp
AvoidConstOrRefDataMembersCheck.cpp
AvoidDoWhileCheck.cpp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -474,10 +474,8 @@ void ProTypeMemberInitCheck::checkMissingMemberInitializer(
// It only includes fields that have not been fixed
SmallPtrSet<const FieldDecl *, 16> AllFieldsToInit;
forEachField(ClassDecl, FieldsToInit, [&](const FieldDecl *F) {
if (!HasRecordClassMemberSet.contains(F)) {
if (HasRecordClassMemberSet.insert(F).second)
AllFieldsToInit.insert(F);
HasRecordClassMemberSet.insert(F);
}
});
if (FieldsToInit.empty())
return;
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-tidy/darwin/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set(LLVM_LINK_COMPONENTS
Support
)

add_clang_library(clangTidyDarwinModule
add_clang_library(clangTidyDarwinModule STATIC
AvoidSpinlockCheck.cpp
DarwinTidyModule.cpp
DispatchOnceNonstaticCheck.cpp
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-tidy/fuchsia/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set(LLVM_LINK_COMPONENTS
Support
)

add_clang_library(clangTidyFuchsiaModule
add_clang_library(clangTidyFuchsiaModule STATIC
DefaultArgumentsCallsCheck.cpp
DefaultArgumentsDeclarationsCheck.cpp
FuchsiaTidyModule.cpp
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-tidy/google/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set(LLVM_LINK_COMPONENTS
Support
)

add_clang_library(clangTidyGoogleModule
add_clang_library(clangTidyGoogleModule STATIC
AvoidCStyleCastsCheck.cpp
AvoidNSObjectNewCheck.cpp
AvoidThrowingObjCExceptionCheck.cpp
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-tidy/hicpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set(LLVM_LINK_COMPONENTS
Support
)

add_clang_library(clangTidyHICPPModule
add_clang_library(clangTidyHICPPModule STATIC
ExceptionBaseclassCheck.cpp
HICPPTidyModule.cpp
IgnoredRemoveResultCheck.cpp
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-tidy/linuxkernel/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set(LLVM_LINK_COMPONENTS
Support
)

add_clang_library(clangTidyLinuxKernelModule
add_clang_library(clangTidyLinuxKernelModule STATIC
LinuxKernelTidyModule.cpp
MustCheckErrsCheck.cpp

Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set(LLVM_LINK_COMPONENTS
Support
)

add_clang_library(clangTidyLLVMModule
add_clang_library(clangTidyLLVMModule STATIC
HeaderGuardCheck.cpp
IncludeOrderCheck.cpp
LLVMTidyModule.cpp
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-tidy/llvmlibc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set(LLVM_LINK_COMPONENTS
Support
)

add_clang_library(clangTidyLLVMLibcModule
add_clang_library(clangTidyLLVMLibcModule STATIC
CalleeNamespaceCheck.cpp
ImplementationInNamespaceCheck.cpp
InlineFunctionDeclCheck.cpp
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-tidy/misc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ add_custom_command(
add_custom_target(genconfusable DEPENDS Confusables.inc)
set_target_properties(genconfusable PROPERTIES FOLDER "Clang Tools Extra/Sourcegenning")

add_clang_library(clangTidyMiscModule
add_clang_library(clangTidyMiscModule STATIC
ConstCorrectnessCheck.cpp
CoroutineHostileRAIICheck.cpp
DefinitionsInHeadersCheck.cpp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,11 @@ static bool mayShadow(const NamedDecl *ND0,
const ConfusableIdentifierCheck::ContextInfo *
ConfusableIdentifierCheck::getContextInfo(const DeclContext *DC) {
const DeclContext *PrimaryContext = DC->getPrimaryContext();
auto It = ContextInfos.find(PrimaryContext);
if (It != ContextInfos.end())
auto [It, Inserted] = ContextInfos.try_emplace(PrimaryContext);
if (!Inserted)
return &It->second;

ContextInfo &Info = ContextInfos[PrimaryContext];
ContextInfo &Info = It->second;
Info.PrimaryContext = PrimaryContext;
Info.NonTransparentContext = PrimaryContext;

Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set(LLVM_LINK_COMPONENTS
Support
)

add_clang_library(clangTidyModernizeModule
add_clang_library(clangTidyModernizeModule STATIC
AvoidBindCheck.cpp
AvoidCArraysCheck.cpp
ConcatNestedNamespacesCheck.cpp
Expand Down
25 changes: 15 additions & 10 deletions clang-tools-extra/clang-tidy/modernize/LoopConvertUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -777,7 +777,7 @@ bool ForLoopIndexUseVisitor::TraverseLambdaCapture(LambdaExpr *LE,
const LambdaCapture *C,
Expr *Init) {
if (C->capturesVariable()) {
const ValueDecl *VDecl = C->getCapturedVar();
ValueDecl *VDecl = C->getCapturedVar();
if (areSameVariable(IndexVar, VDecl)) {
// FIXME: if the index is captured, it will count as an usage and the
// alias (if any) won't work, because it is only used in case of having
Expand All @@ -787,6 +787,8 @@ bool ForLoopIndexUseVisitor::TraverseLambdaCapture(LambdaExpr *LE,
: Usage::UK_CaptureByRef,
C->getLocation()));
}
if (VDecl->isInitCapture())
TraverseStmtImpl(cast<VarDecl>(VDecl)->getInit());
}
return VisitorBase::TraverseLambdaCapture(LE, C, Init);
}
Expand Down Expand Up @@ -816,6 +818,17 @@ bool ForLoopIndexUseVisitor::VisitDeclStmt(DeclStmt *S) {
return true;
}

bool ForLoopIndexUseVisitor::TraverseStmtImpl(Stmt *S) {
// All this pointer swapping is a mechanism for tracking immediate parentage
// of Stmts.
const Stmt *OldNextParent = NextStmtParent;
CurrStmtParent = NextStmtParent;
NextStmtParent = S;
bool Result = VisitorBase::TraverseStmt(S);
NextStmtParent = OldNextParent;
return Result;
}

bool ForLoopIndexUseVisitor::TraverseStmt(Stmt *S) {
// If this is an initialization expression for a lambda capture, prune the
// traversal so that we don't end up diagnosing the contained DeclRefExpr as
Expand All @@ -828,15 +841,7 @@ bool ForLoopIndexUseVisitor::TraverseStmt(Stmt *S) {
return true;
}
}

// All this pointer swapping is a mechanism for tracking immediate parentage
// of Stmts.
const Stmt *OldNextParent = NextStmtParent;
CurrStmtParent = NextStmtParent;
NextStmtParent = S;
bool Result = VisitorBase::TraverseStmt(S);
NextStmtParent = OldNextParent;
return Result;
return TraverseStmtImpl(S);
}

std::string VariableNamer::createIndexName() {
Expand Down
2 changes: 2 additions & 0 deletions clang-tools-extra/clang-tidy/modernize/LoopConvertUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,8 @@ class ForLoopIndexUseVisitor
bool VisitDeclStmt(DeclStmt *S);
bool TraverseStmt(Stmt *S);

bool TraverseStmtImpl(Stmt *S);

/// Add an expression to the list of expressions on which the container
/// expression depends.
void addComponent(const Expr *E);
Expand Down
226 changes: 135 additions & 91 deletions clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "UseStartsEndsWithCheck.h"

#include "../utils/ASTUtils.h"
#include "../utils/OptionsUtils.h"
#include "clang/Lex/Lexer.h"

Expand All @@ -16,13 +17,71 @@
using namespace clang::ast_matchers;

namespace clang::tidy::modernize {
struct NotLengthExprForStringNode {
NotLengthExprForStringNode(std::string ID, DynTypedNode Node,
ASTContext *Context)
: ID(std::move(ID)), Node(std::move(Node)), Context(Context) {}
bool operator()(const internal::BoundNodesMap &Nodes) const {
// Match a string literal and an integer size or strlen() call.
if (const auto *StringLiteralNode = Nodes.getNodeAs<StringLiteral>(ID)) {
if (const auto *IntegerLiteralSizeNode = Node.get<IntegerLiteral>()) {
return StringLiteralNode->getLength() !=
IntegerLiteralSizeNode->getValue().getZExtValue();
}

if (const auto *StrlenNode = Node.get<CallExpr>()) {
if (StrlenNode->getDirectCallee()->getName() != "strlen" ||
StrlenNode->getNumArgs() != 1) {
return true;
}

if (const auto *StrlenArgNode = dyn_cast<StringLiteral>(
StrlenNode->getArg(0)->IgnoreParenImpCasts())) {
return StrlenArgNode->getLength() != StringLiteralNode->getLength();
}
}
}

// Match a string variable and a call to length() or size().
if (const auto *ExprNode = Nodes.getNodeAs<Expr>(ID)) {
if (const auto *MemberCallNode = Node.get<CXXMemberCallExpr>()) {
const CXXMethodDecl *MethodDeclNode = MemberCallNode->getMethodDecl();
const StringRef Name = MethodDeclNode->getName();
if (!MethodDeclNode->isConst() || MethodDeclNode->getNumParams() != 0 ||
(Name != "size" && Name != "length")) {
return true;
}

if (const auto *OnNode =
dyn_cast<Expr>(MemberCallNode->getImplicitObjectArgument())) {
return !utils::areStatementsIdentical(OnNode->IgnoreParenImpCasts(),
ExprNode->IgnoreParenImpCasts(),
*Context);
}
}
}

return true;
}

private:
std::string ID;
DynTypedNode Node;
ASTContext *Context;
};

AST_MATCHER_P(Expr, lengthExprForStringNode, std::string, ID) {
return Builder->removeBindings(NotLengthExprForStringNode(
ID, DynTypedNode::create(Node), &(Finder->getASTContext())));
}

UseStartsEndsWithCheck::UseStartsEndsWithCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context) {}

void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {
const auto ZeroLiteral = integerLiteral(equals(0));

const auto HasStartsWithMethodWithName = [](const std::string &Name) {
return hasMethod(
cxxMethodDecl(hasName(Name), isConst(), parameterCountIs(1))
Expand All @@ -32,119 +91,104 @@ void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {
anyOf(HasStartsWithMethodWithName("starts_with"),
HasStartsWithMethodWithName("startsWith"),
HasStartsWithMethodWithName("startswith"));
const auto ClassWithStartsWithFunction = cxxRecordDecl(anyOf(
HasStartsWithMethod, hasAnyBase(hasType(hasCanonicalType(hasDeclaration(
cxxRecordDecl(HasStartsWithMethod)))))));
const auto OnClassWithStartsWithFunction =
on(hasType(hasCanonicalType(hasDeclaration(cxxRecordDecl(
anyOf(HasStartsWithMethod,
hasAnyBase(hasType(hasCanonicalType(
hasDeclaration(cxxRecordDecl(HasStartsWithMethod)))))))))));

const auto HasEndsWithMethodWithName = [](const std::string &Name) {
return hasMethod(
cxxMethodDecl(hasName(Name), isConst(), parameterCountIs(1))
.bind("ends_with_fun"));
};
const auto HasEndsWithMethod = anyOf(HasEndsWithMethodWithName("ends_with"),
HasEndsWithMethodWithName("endsWith"),
HasEndsWithMethodWithName("endswith"));
const auto OnClassWithEndsWithFunction =
on(expr(hasType(hasCanonicalType(hasDeclaration(cxxRecordDecl(
anyOf(HasEndsWithMethod,
hasAnyBase(hasType(hasCanonicalType(hasDeclaration(
cxxRecordDecl(HasEndsWithMethod)))))))))))
.bind("haystack"));

// Case 1: X.find(Y) [!=]= 0 -> starts_with.
const auto FindExpr = cxxMemberCallExpr(
// A method call with no second argument or the second argument is zero...
anyOf(argumentCountIs(1), hasArgument(1, ZeroLiteral)),
// ... named find...
callee(cxxMethodDecl(hasName("find")).bind("find_fun")),
// ... on a class with a starts_with function.
on(hasType(
hasCanonicalType(hasDeclaration(ClassWithStartsWithFunction)))),
// Bind search expression.
hasArgument(0, expr().bind("search_expr")));
OnClassWithStartsWithFunction, hasArgument(0, expr().bind("needle")));

// Case 2: X.rfind(Y, 0) [!=]= 0 -> starts_with.
const auto RFindExpr = cxxMemberCallExpr(
// A method call with a second argument of zero...
hasArgument(1, ZeroLiteral),
// ... named rfind...
callee(cxxMethodDecl(hasName("rfind")).bind("find_fun")),
// ... on a class with a starts_with function.
on(hasType(
hasCanonicalType(hasDeclaration(ClassWithStartsWithFunction)))),
// Bind search expression.
hasArgument(0, expr().bind("search_expr")));

// Match a string literal and an integer or strlen() call matching the length.
const auto HasStringLiteralAndLengthArgs = [](const auto StringArgIndex,
const auto LengthArgIndex) {
return allOf(
hasArgument(StringArgIndex, stringLiteral().bind("string_literal_arg")),
hasArgument(LengthArgIndex,
anyOf(integerLiteral().bind("integer_literal_size_arg"),
callExpr(callee(functionDecl(parameterCountIs(1),
hasName("strlen"))),
hasArgument(0, stringLiteral().bind(
"strlen_arg"))))));
};

// Match a string variable and a call to length() or size().
const auto HasStringVariableAndSizeCallArgs = [](const auto StringArgIndex,
const auto LengthArgIndex) {
return allOf(
hasArgument(StringArgIndex, declRefExpr(hasDeclaration(
decl().bind("string_var_decl")))),
hasArgument(LengthArgIndex,
cxxMemberCallExpr(
callee(cxxMethodDecl(isConst(), parameterCountIs(0),
hasAnyName("size", "length"))),
on(declRefExpr(
to(decl(equalsBoundNode("string_var_decl"))))))));
};

// Match either one of the two cases above.
const auto HasStringAndLengthArgs =
[HasStringLiteralAndLengthArgs, HasStringVariableAndSizeCallArgs](
const auto StringArgIndex, const auto LengthArgIndex) {
return anyOf(
HasStringLiteralAndLengthArgs(StringArgIndex, LengthArgIndex),
HasStringVariableAndSizeCallArgs(StringArgIndex, LengthArgIndex));
};
OnClassWithStartsWithFunction, hasArgument(0, expr().bind("needle")));

// Case 3: X.compare(0, LEN(Y), Y) [!=]= 0 -> starts_with.
const auto CompareExpr = cxxMemberCallExpr(
// A method call with three arguments...
argumentCountIs(3),
// ... where the first argument is zero...
hasArgument(0, ZeroLiteral),
// ... named compare...
argumentCountIs(3), hasArgument(0, ZeroLiteral),
callee(cxxMethodDecl(hasName("compare")).bind("find_fun")),
// ... on a class with a starts_with function...
on(hasType(
hasCanonicalType(hasDeclaration(ClassWithStartsWithFunction)))),
// ... where the third argument is some string and the second a length.
HasStringAndLengthArgs(2, 1),
// Bind search expression.
hasArgument(2, expr().bind("search_expr")));
OnClassWithStartsWithFunction, hasArgument(2, expr().bind("needle")),
hasArgument(1, lengthExprForStringNode("needle")));

// Case 4: X.compare(LEN(X) - LEN(Y), LEN(Y), Y) [!=]= 0 -> ends_with.
const auto CompareEndsWithExpr = cxxMemberCallExpr(
argumentCountIs(3),
callee(cxxMethodDecl(hasName("compare")).bind("find_fun")),
OnClassWithEndsWithFunction, hasArgument(2, expr().bind("needle")),
hasArgument(1, lengthExprForStringNode("needle")),
hasArgument(0,
binaryOperator(hasOperatorName("-"),
hasLHS(lengthExprForStringNode("haystack")),
hasRHS(lengthExprForStringNode("needle")))));

// All cases comparing to 0.
Finder->addMatcher(
// Match [=!]= with a zero on one side and (r?)find|compare on the other.
binaryOperator(
hasAnyOperatorName("==", "!="),
hasOperands(cxxMemberCallExpr(anyOf(FindExpr, RFindExpr, CompareExpr))
hasOperands(cxxMemberCallExpr(anyOf(FindExpr, RFindExpr, CompareExpr,
CompareEndsWithExpr))
.bind("find_expr"),
ZeroLiteral))
.bind("expr"),
this);

// Case 5: X.rfind(Y) [!=]= LEN(X) - LEN(Y) -> ends_with.
Finder->addMatcher(
binaryOperator(
hasAnyOperatorName("==", "!="),
hasOperands(
cxxMemberCallExpr(
anyOf(
argumentCountIs(1),
allOf(argumentCountIs(2),
hasArgument(
1,
anyOf(declRefExpr(to(varDecl(hasName("npos")))),
memberExpr(member(hasName("npos"))))))),
callee(cxxMethodDecl(hasName("rfind")).bind("find_fun")),
OnClassWithEndsWithFunction,
hasArgument(0, expr().bind("needle")))
.bind("find_expr"),
binaryOperator(hasOperatorName("-"),
hasLHS(lengthExprForStringNode("haystack")),
hasRHS(lengthExprForStringNode("needle")))))
.bind("expr"),
this);
}

void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
const auto *ComparisonExpr = Result.Nodes.getNodeAs<BinaryOperator>("expr");
const auto *FindExpr = Result.Nodes.getNodeAs<CXXMemberCallExpr>("find_expr");
const auto *FindFun = Result.Nodes.getNodeAs<CXXMethodDecl>("find_fun");
const auto *SearchExpr = Result.Nodes.getNodeAs<Expr>("search_expr");
const auto *SearchExpr = Result.Nodes.getNodeAs<Expr>("needle");
const auto *StartsWithFunction =
Result.Nodes.getNodeAs<CXXMethodDecl>("starts_with_fun");

const auto *StringLiteralArg =
Result.Nodes.getNodeAs<StringLiteral>("string_literal_arg");
const auto *IntegerLiteralSizeArg =
Result.Nodes.getNodeAs<IntegerLiteral>("integer_literal_size_arg");
const auto *StrlenArg = Result.Nodes.getNodeAs<StringLiteral>("strlen_arg");

// Filter out compare cases where the length does not match string literal.
if (StringLiteralArg && IntegerLiteralSizeArg &&
StringLiteralArg->getLength() !=
IntegerLiteralSizeArg->getValue().getZExtValue()) {
return;
}

if (StringLiteralArg && StrlenArg &&
StringLiteralArg->getLength() != StrlenArg->getLength()) {
return;
}
const auto *EndsWithFunction =
Result.Nodes.getNodeAs<CXXMethodDecl>("ends_with_fun");
assert(bool(StartsWithFunction) != bool(EndsWithFunction));
const CXXMethodDecl *ReplacementFunction =
StartsWithFunction ? StartsWithFunction : EndsWithFunction;

if (ComparisonExpr->getBeginLoc().isMacroID()) {
return;
Expand All @@ -154,26 +198,26 @@ void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {

auto Diagnostic =
diag(FindExpr->getExprLoc(), "use %0 instead of %1() %select{==|!=}2 0")
<< StartsWithFunction->getName() << FindFun->getName() << Neg;
<< ReplacementFunction->getName() << FindFun->getName() << Neg;

// Remove possible arguments after search expression and ' [!=]= 0' suffix.
// Remove possible arguments after search expression and ' [!=]= .+' suffix.
Diagnostic << FixItHint::CreateReplacement(
CharSourceRange::getTokenRange(
Lexer::getLocForEndOfToken(SearchExpr->getEndLoc(), 0,
*Result.SourceManager, getLangOpts()),
ComparisonExpr->getEndLoc()),
")");

// Remove possible '0 [!=]= ' prefix.
// Remove possible '.+ [!=]= ' prefix.
Diagnostic << FixItHint::CreateRemoval(CharSourceRange::getCharRange(
ComparisonExpr->getBeginLoc(), FindExpr->getBeginLoc()));

// Replace method name by 'starts_with'.
// Replace method name by '(starts|ends)_with'.
// Remove possible arguments before search expression.
Diagnostic << FixItHint::CreateReplacement(
CharSourceRange::getCharRange(FindExpr->getExprLoc(),
SearchExpr->getBeginLoc()),
(StartsWithFunction->getName() + "(").str());
(ReplacementFunction->getName() + "(").str());

// Add possible negation '!'.
if (Neg) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
namespace clang::tidy::modernize {

/// Checks for common roundabout ways to express ``starts_with`` and
/// ``ends_with`` and suggests replacing with ``starts_with`` when the method is
/// ``ends_with`` and suggests replacing with the simpler method when it is
/// available. Notably, this will work with ``std::string`` and
/// ``std::string_view``.
///
Expand Down
7 changes: 4 additions & 3 deletions clang-tools-extra/clang-tidy/modernize/UseStdFormatCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ void UseStdFormatCheck::registerPPCallbacks(const SourceManager &SM,
Preprocessor *PP,
Preprocessor *ModuleExpanderPP) {
IncludeInserter.registerPreprocessor(PP);
this->PP = PP;
}

void UseStdFormatCheck::registerMatchers(MatchFinder *Finder) {
Expand Down Expand Up @@ -75,9 +76,9 @@ void UseStdFormatCheck::check(const MatchFinder::MatchResult &Result) {

utils::FormatStringConverter::Configuration ConverterConfig;
ConverterConfig.StrictMode = StrictMode;
utils::FormatStringConverter Converter(Result.Context, StrFormat,
FormatArgOffset, ConverterConfig,
getLangOpts());
utils::FormatStringConverter Converter(
Result.Context, StrFormat, FormatArgOffset, ConverterConfig,
getLangOpts(), *Result.SourceManager, *PP);
const Expr *StrFormatCall = StrFormat->getCallee();
if (!Converter.canApply()) {
diag(StrFormat->getBeginLoc(),
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/modernize/UseStdFormatCheck.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class UseStdFormatCheck : public ClangTidyCheck {
StringRef ReplacementFormatFunction;
utils::IncludeInserter IncludeInserter;
std::optional<StringRef> MaybeHeaderToInclude;
Preprocessor *PP = nullptr;
};

} // namespace clang::tidy::modernize
Expand Down
4 changes: 3 additions & 1 deletion clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ void UseStdPrintCheck::registerPPCallbacks(const SourceManager &SM,
Preprocessor *PP,
Preprocessor *ModuleExpanderPP) {
IncludeInserter.registerPreprocessor(PP);
this->PP = PP;
}

static clang::ast_matchers::StatementMatcher
Expand Down Expand Up @@ -131,7 +132,8 @@ void UseStdPrintCheck::check(const MatchFinder::MatchResult &Result) {
ConverterConfig.StrictMode = StrictMode;
ConverterConfig.AllowTrailingNewlineRemoval = true;
utils::FormatStringConverter Converter(
Result.Context, Printf, FormatArgOffset, ConverterConfig, getLangOpts());
Result.Context, Printf, FormatArgOffset, ConverterConfig, getLangOpts(),
*Result.SourceManager, *PP);
const Expr *PrintfCall = Printf->getCallee();
const StringRef ReplacementFunction = Converter.usePrintNewlineFunction()
? ReplacementPrintlnFunction
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class UseStdPrintCheck : public ClangTidyCheck {
}

private:
Preprocessor *PP;
bool StrictMode;
std::vector<StringRef> PrintfLikeFunctions;
std::vector<StringRef> FprintfLikeFunctions;
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-tidy/mpi/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set(LLVM_LINK_COMPONENTS
Support
)

add_clang_library(clangTidyMPIModule
add_clang_library(clangTidyMPIModule STATIC
BufferDerefCheck.cpp
MPITidyModule.cpp
TypeMismatchCheck.cpp
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-tidy/objc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set(LLVM_LINK_COMPONENTS
Support
)

add_clang_library(clangTidyObjCModule
add_clang_library(clangTidyObjCModule STATIC
AssertEquals.cpp
AvoidNSErrorInitCheck.cpp
DeallocInCategoryCheck.cpp
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-tidy/openmp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set(LLVM_LINK_COMPONENTS
Support
)

add_clang_library(clangTidyOpenMPModule
add_clang_library(clangTidyOpenMPModule STATIC
ExceptionEscapeCheck.cpp
OpenMPTidyModule.cpp
UseDefaultNoneCheck.cpp
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-tidy/performance/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set(LLVM_LINK_COMPONENTS
Support
)

add_clang_library(clangTidyPerformanceModule
add_clang_library(clangTidyPerformanceModule STATIC
AvoidEndlCheck.cpp
EnumSizeCheck.cpp
FasterStringFindCheck.cpp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,9 @@ void MoveConstArgCheck::check(const MatchFinder::MatchResult &Result) {
}

if (const CXXRecordDecl *RecordDecl = ArgType->getAsCXXRecordDecl();
RecordDecl && !(RecordDecl->hasMoveConstructor() &&
RecordDecl->hasMoveAssignment())) {
RecordDecl && RecordDecl->hasDefinition() &&
!(RecordDecl->hasMoveConstructor() &&
RecordDecl->hasMoveAssignment())) {
const bool MissingMoveAssignment = !RecordDecl->hasMoveAssignment();
const bool MissingMoveConstructor = !RecordDecl->hasMoveConstructor();
const bool MissingBoth = MissingMoveAssignment && MissingMoveConstructor;
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-tidy/plugin/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
add_clang_library(clangTidyPlugin
add_clang_library(clangTidyPlugin STATIC
ClangTidyPlugin.cpp

LINK_LIBS
Expand Down
3 changes: 2 additions & 1 deletion clang-tools-extra/clang-tidy/portability/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ set(LLVM_LINK_COMPONENTS
TargetParser
)

add_clang_library(clangTidyPortabilityModule
add_clang_library(clangTidyPortabilityModule STATIC
PortabilityTidyModule.cpp
RestrictSystemIncludesCheck.cpp
SIMDIntrinsicsCheck.cpp
StdAllocatorConstCheck.cpp
TemplateVirtualMemberFunctionCheck.cpp

LINK_LIBS
clangTidy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "RestrictSystemIncludesCheck.h"
#include "SIMDIntrinsicsCheck.h"
#include "StdAllocatorConstCheck.h"
#include "TemplateVirtualMemberFunctionCheck.h"

namespace clang::tidy {
namespace portability {
Expand All @@ -25,6 +26,8 @@ class PortabilityModule : public ClangTidyModule {
"portability-simd-intrinsics");
CheckFactories.registerCheck<StdAllocatorConstCheck>(
"portability-std-allocator-const");
CheckFactories.registerCheck<TemplateVirtualMemberFunctionCheck>(
"portability-template-virtual-member-function");
}
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//===--- TemplateVirtualMemberFunctionCheck.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 "TemplateVirtualMemberFunctionCheck.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"

using namespace clang::ast_matchers;

namespace clang::tidy::portability {
namespace {
AST_MATCHER(CXXMethodDecl, isUsed) { return Node.isUsed(); }
} // namespace

void TemplateVirtualMemberFunctionCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(
cxxMethodDecl(ofClass(classTemplateSpecializationDecl(
unless(isExplicitTemplateSpecialization()))
.bind("specialization")),
isVirtual(), unless(isUsed()),
unless(cxxDestructorDecl(isDefaulted())))
.bind("method"),
this);
}

void TemplateVirtualMemberFunctionCheck::check(
const MatchFinder::MatchResult &Result) {
const auto *ImplicitSpecialization =
Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("specialization");
const auto *MethodDecl = Result.Nodes.getNodeAs<CXXMethodDecl>("method");

diag(MethodDecl->getLocation(),
"unspecified virtual member function instantiation; the virtual "
"member function is not instantiated but it might be with a "
"different compiler");
diag(ImplicitSpecialization->getPointOfInstantiation(),
"template instantiated here", DiagnosticIDs::Note);
}

} // namespace clang::tidy::portability
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//===--- TemplateVirtualMemberFunctionCheck.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_PORTABILITY_TEMPLATEVIRTUALMEMBERFUNCTIONCHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PORTABILITY_TEMPLATEVIRTUALMEMBERFUNCTIONCHECK_H

#include "../ClangTidyCheck.h"

namespace clang::tidy::portability {

/// Upon instantiating a template class, non-virtual member functions don't have
/// to be instantiated unless they are used. Virtual member function
/// instantiation on the other hand is unspecified and depends on the
/// implementation of the compiler. This check intends to find cases when a
/// virtual member function is not instantiated but it might be with a different
/// compiler.
///
/// For the user-facing documentation see:
/// http://clang.llvm.org/extra/clang-tidy/checks/portability/template-virtual-member-function.html
class TemplateVirtualMemberFunctionCheck : public ClangTidyCheck {
public:
TemplateVirtualMemberFunctionCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context) {}
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
return LangOpts.CPlusPlus;
}
};

} // namespace clang::tidy::portability

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PORTABILITY_TEMPLATEVIRTUALMEMBERFUNCTIONCHECK_H
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-tidy/readability/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set(LLVM_LINK_COMPONENTS
Support
)

add_clang_library(clangTidyReadabilityModule
add_clang_library(clangTidyReadabilityModule STATIC
AvoidConstParamsInDecls.cpp
AvoidNestedConditionalOperatorCheck.cpp
AvoidReturnWithVoidValueCheck.cpp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1135,6 +1135,9 @@ StyleKind IdentifierNamingCheck::findStyleKind(
if (isa<TypeAliasDecl>(D) && NamingStyles[SK_TypeAlias])
return SK_TypeAlias;

if (isa<NamespaceAliasDecl>(D) && NamingStyles[SK_Namespace])
return SK_Namespace;

if (const auto *Decl = dyn_cast<NamespaceDecl>(D)) {
if (Decl->isAnonymousNamespace())
return SK_Invalid;
Expand Down
32 changes: 18 additions & 14 deletions clang-tools-extra/clang-tidy/rename_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@
#
# ===-----------------------------------------------------------------------===#

from __future__ import unicode_literals

import argparse
import glob
import io
import os
import re
import sys
from typing import List


def replaceInFileRegex(fileName, sFrom, sTo):
def replaceInFileRegex(fileName: str, sFrom: str, sTo: str) -> None:
if sFrom == sTo:
return

Expand All @@ -35,7 +35,7 @@ def replaceInFileRegex(fileName, sFrom, sTo):
f.write(txt)


def replaceInFile(fileName, sFrom, sTo):
def replaceInFile(fileName: str, sFrom: str, sTo: str) -> None:
if sFrom == sTo:
return
txt = None
Expand All @@ -51,7 +51,7 @@ def replaceInFile(fileName, sFrom, sTo):
f.write(txt)


def generateCommentLineHeader(filename):
def generateCommentLineHeader(filename: str) -> str:
return "".join(
[
"//===--- ",
Expand All @@ -63,7 +63,7 @@ def generateCommentLineHeader(filename):
)


def generateCommentLineSource(filename):
def generateCommentLineSource(filename: str) -> str:
return "".join(
[
"//===--- ",
Expand All @@ -75,7 +75,7 @@ def generateCommentLineSource(filename):
)


def fileRename(fileName, sFrom, sTo):
def fileRename(fileName: str, sFrom: str, sTo: str) -> str:
if sFrom not in fileName or sFrom == sTo:
return fileName
newFileName = fileName.replace(sFrom, sTo)
Expand All @@ -84,7 +84,7 @@ def fileRename(fileName, sFrom, sTo):
return newFileName


def deleteMatchingLines(fileName, pattern):
def deleteMatchingLines(fileName: str, pattern: str) -> bool:
lines = None
with io.open(fileName, "r", encoding="utf8") as f:
lines = f.readlines()
Expand All @@ -101,7 +101,7 @@ def deleteMatchingLines(fileName, pattern):
return True


def getListOfFiles(clang_tidy_path):
def getListOfFiles(clang_tidy_path: str) -> List[str]:
files = glob.glob(os.path.join(clang_tidy_path, "**"), recursive=True)
files += [
os.path.normpath(os.path.join(clang_tidy_path, "../docs/ReleaseNotes.rst"))
Expand All @@ -124,7 +124,7 @@ def getListOfFiles(clang_tidy_path):

# Adapts the module's CMakelist file. Returns 'True' if it could add a new
# entry and 'False' if the entry already existed.
def adapt_cmake(module_path, check_name_camel):
def adapt_cmake(module_path: str, check_name_camel: str) -> bool:
filename = os.path.join(module_path, "CMakeLists.txt")
with io.open(filename, "r", encoding="utf8") as f:
lines = f.readlines()
Expand Down Expand Up @@ -153,7 +153,9 @@ def adapt_cmake(module_path, check_name_camel):


# Modifies the module to include the new check.
def adapt_module(module_path, module, check_name, check_name_camel):
def adapt_module(
module_path: str, module: str, check_name: str, check_name_camel: str
) -> None:
modulecpp = next(
iter(
filter(
Expand Down Expand Up @@ -204,7 +206,9 @@ def adapt_module(module_path, module, check_name, check_name_camel):


# Adds a release notes entry.
def add_release_notes(clang_tidy_path, old_check_name, new_check_name):
def add_release_notes(
clang_tidy_path: str, old_check_name: str, new_check_name: str
) -> None:
filename = os.path.normpath(
os.path.join(clang_tidy_path, "../docs/ReleaseNotes.rst")
)
Expand Down Expand Up @@ -262,7 +266,7 @@ def add_release_notes(clang_tidy_path, old_check_name, new_check_name):
f.write(line)


def main():
def main() -> None:
parser = argparse.ArgumentParser(description="Rename clang-tidy check.")
parser.add_argument("old_check_name", type=str, help="Old check name.")
parser.add_argument("new_check_name", type=str, help="New check name.")
Expand Down Expand Up @@ -311,7 +315,7 @@ def main():
"Check name '%s' not found in %s. Exiting."
% (check_name_camel, cmake_lists)
)
return 1
sys.exit(1)

modulecpp = next(
iter(
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-tidy/tool/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ set(LLVM_LINK_COMPONENTS
# Needed by LLVM's CMake checks because this file defines multiple targets.
set(LLVM_OPTIONAL_SOURCES ClangTidyMain.cpp ClangTidyToolMain.cpp)

add_clang_library(clangTidyMain
add_clang_library(clangTidyMain STATIC
ClangTidyMain.cpp

LINK_LIBS
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-tidy/utils/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set(LLVM_LINK_COMPONENTS
Support
)

add_clang_library(clangTidyUtils
add_clang_library(clangTidyUtils STATIC
Aliasing.cpp
ASTUtils.cpp
BracesAroundStatement.cpp
Expand Down
65 changes: 60 additions & 5 deletions clang-tools-extra/clang-tidy/utils/FormatStringConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Lex/Lexer.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Tooling/FixIt.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/Debug.h"
Expand Down Expand Up @@ -195,11 +196,10 @@ static bool castMismatchedIntegerTypes(const CallExpr *Call, bool StrictMode) {
return false;
}

FormatStringConverter::FormatStringConverter(ASTContext *ContextIn,
const CallExpr *Call,
unsigned FormatArgOffset,
const Configuration ConfigIn,
const LangOptions &LO)
FormatStringConverter::FormatStringConverter(
ASTContext *ContextIn, const CallExpr *Call, unsigned FormatArgOffset,
const Configuration ConfigIn, const LangOptions &LO, SourceManager &SM,
Preprocessor &PP)
: Context(ContextIn), Config(ConfigIn),
CastMismatchedIntegerTypes(
castMismatchedIntegerTypes(Call, ConfigIn.StrictMode)),
Expand All @@ -208,11 +208,22 @@ FormatStringConverter::FormatStringConverter(ASTContext *ContextIn,
assert(ArgsOffset <= NumArgs);
FormatExpr = llvm::dyn_cast<StringLiteral>(
Args[FormatArgOffset]->IgnoreImplicitAsWritten());

if (!FormatExpr || !FormatExpr->isOrdinary()) {
// Function must have a narrow string literal as its first argument.
conversionNotPossible("first argument is not a narrow string literal");
return;
}

if (const std::optional<StringRef> MaybeMacroName =
formatStringContainsUnreplaceableMacro(Call, FormatExpr, SM, PP);
MaybeMacroName) {
conversionNotPossible(
("format string contains unreplaceable macro '" + *MaybeMacroName + "'")
.str());
return;
}

PrintfFormatString = FormatExpr->getString();

// Assume that the output will be approximately the same size as the input,
Expand All @@ -230,6 +241,50 @@ FormatStringConverter::FormatStringConverter(ASTContext *ContextIn,
finalizeFormatText();
}

std::optional<StringRef>
FormatStringConverter::formatStringContainsUnreplaceableMacro(
const CallExpr *Call, const StringLiteral *FormatExpr, SourceManager &SM,
Preprocessor &PP) {
// If a macro invocation surrounds the entire call then we don't want that to
// inhibit conversion. The whole format string will appear to come from that
// macro, as will the function call.
std::optional<StringRef> MaybeSurroundingMacroName;
if (SourceLocation BeginCallLoc = Call->getBeginLoc();
BeginCallLoc.isMacroID())
MaybeSurroundingMacroName =
Lexer::getImmediateMacroName(BeginCallLoc, SM, PP.getLangOpts());

for (auto I = FormatExpr->tokloc_begin(), E = FormatExpr->tokloc_end();
I != E; ++I) {
const SourceLocation &TokenLoc = *I;
if (TokenLoc.isMacroID()) {
const StringRef MacroName =
Lexer::getImmediateMacroName(TokenLoc, SM, PP.getLangOpts());

if (MaybeSurroundingMacroName != MacroName) {
// glibc uses __PRI64_PREFIX and __PRIPTR_PREFIX to define the prefixes
// for types that change size so we must look for multiple prefixes.
if (!MacroName.starts_with("PRI") && !MacroName.starts_with("__PRI"))
return MacroName;

const SourceLocation TokenSpellingLoc = SM.getSpellingLoc(TokenLoc);
const OptionalFileEntryRef MaybeFileEntry =
SM.getFileEntryRefForID(SM.getFileID(TokenSpellingLoc));
if (!MaybeFileEntry)
return MacroName;

HeaderSearch &HS = PP.getHeaderSearchInfo();
// Check if the file is a system header
if (!isSystem(HS.getFileDirFlavor(*MaybeFileEntry)) ||
llvm::sys::path::filename(MaybeFileEntry->getName()) !=
"inttypes.h")
return MacroName;
}
}
}
return std::nullopt;
}

void FormatStringConverter::emitAlignment(const PrintfSpecifier &FS,
std::string &FormatSpec) {
ConversionSpecifier::Kind ArgKind = FS.getConversionSpecifier().getKind();
Expand Down
7 changes: 6 additions & 1 deletion clang-tools-extra/clang-tidy/utils/FormatStringConverter.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ class FormatStringConverter

FormatStringConverter(ASTContext *Context, const CallExpr *Call,
unsigned FormatArgOffset, Configuration Config,
const LangOptions &LO);
const LangOptions &LO, SourceManager &SM,
Preprocessor &PP);

bool canApply() const { return ConversionNotPossibleReason.empty(); }
const std::string &conversionNotPossibleReason() const {
Expand Down Expand Up @@ -110,6 +111,10 @@ class FormatStringConverter

void appendFormatText(StringRef Text);
void finalizeFormatText();
static std::optional<StringRef>
formatStringContainsUnreplaceableMacro(const CallExpr *CallExpr,
const StringLiteral *FormatExpr,
SourceManager &SM, Preprocessor &PP);
bool conversionNotPossible(std::string Reason) {
ConversionNotPossibleReason = std::move(Reason);
return false;
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-tidy/zircon/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set(LLVM_LINK_COMPONENTS
Support
)

add_clang_library(clangTidyZirconModule
add_clang_library(clangTidyZirconModule STATIC
TemporaryObjectsCheck.cpp
ZirconTidyModule.cpp

Expand Down
7 changes: 6 additions & 1 deletion clang-tools-extra/clangd/AST.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,13 @@ getQualification(ASTContext &Context, const DeclContext *DestContext,
// since we stored inner-most parent first.
std::string Result;
llvm::raw_string_ostream OS(Result);
for (const auto *Parent : llvm::reverse(Parents))
for (const auto *Parent : llvm::reverse(Parents)) {
if (Parent != *Parents.rbegin() && Parent->isDependent() &&
Parent->getAsRecordDecl() &&
Parent->getAsRecordDecl()->getDescribedClassTemplate())
OS << "template ";
Parent->print(OS, Context.getPrintingPolicy());
}
return OS.str();
}

Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ endif()
include_directories(BEFORE "${CMAKE_CURRENT_BINARY_DIR}/../clang-tidy")
include_directories(BEFORE "${CMAKE_CURRENT_SOURCE_DIR}/../include-cleaner/include")

add_clang_library(clangDaemon
add_clang_library(clangDaemon STATIC
AST.cpp
ASTSignals.cpp
ClangdLSPServer.cpp
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/clangd/ClangdServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,7 @@ void ClangdServer::codeComplete(PathRef File, Position Pos,

CodeCompleteOpts.MainFileSignals = IP->Signals;
CodeCompleteOpts.AllScopes = Config::current().Completion.AllScopes;
CodeCompleteOpts.ArgumentLists = Config::current().Completion.ArgumentLists;
// FIXME(ibiryukov): even if Preamble is non-null, we may want to check
// both the old and the new version in case only one of them matches.
CodeCompleteResult Result = clangd::codeComplete(
Expand Down
26 changes: 18 additions & 8 deletions clang-tools-extra/clangd/CodeComplete.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "AST.h"
#include "CodeCompletionStrings.h"
#include "Compiler.h"
#include "Config.h"
#include "ExpectedTypes.h"
#include "Feature.h"
#include "FileDistance.h"
Expand Down Expand Up @@ -350,8 +351,7 @@ struct CodeCompletionBuilder {
CodeCompletionContext::Kind ContextKind,
const CodeCompleteOptions &Opts,
bool IsUsingDeclaration, tok::TokenKind NextTokenKind)
: ASTCtx(ASTCtx),
EnableFunctionArgSnippets(Opts.EnableFunctionArgSnippets),
: ASTCtx(ASTCtx), ArgumentLists(Opts.ArgumentLists),
IsUsingDeclaration(IsUsingDeclaration), NextTokenKind(NextTokenKind) {
Completion.Deprecated = true; // cleared by any non-deprecated overload.
add(C, SemaCCS, ContextKind);
Expand Down Expand Up @@ -561,14 +561,23 @@ struct CodeCompletionBuilder {
}

std::string summarizeSnippet() const {
/// localize ArgumentLists tests for better readability
const bool None = ArgumentLists == Config::ArgumentListsPolicy::None;
const bool Open =
ArgumentLists == Config::ArgumentListsPolicy::OpenDelimiter;
const bool Delim = ArgumentLists == Config::ArgumentListsPolicy::Delimiters;
const bool Full =
ArgumentLists == Config::ArgumentListsPolicy::FullPlaceholders ||
(!None && !Open && !Delim); // <-- failsafe: Full is default

if (IsUsingDeclaration)
return "";
auto *Snippet = onlyValue<&BundledEntry::SnippetSuffix>();
if (!Snippet)
// All bundles are function calls.
// FIXME(ibiryukov): sometimes add template arguments to a snippet, e.g.
// we need to complete 'forward<$1>($0)'.
return "($0)";
return None ? "" : (Open ? "(" : "($0)");

if (Snippet->empty())
return "";
Expand Down Expand Up @@ -607,7 +616,7 @@ struct CodeCompletionBuilder {
return "";
}
}
if (EnableFunctionArgSnippets)
if (Full)
return *Snippet;

// Replace argument snippets with a simplified pattern.
Expand All @@ -622,9 +631,9 @@ struct CodeCompletionBuilder {

bool EmptyArgs = llvm::StringRef(*Snippet).ends_with("()");
if (Snippet->front() == '<')
return EmptyArgs ? "<$1>()$0" : "<$1>($0)";
return None ? "" : (Open ? "<" : (EmptyArgs ? "<$1>()$0" : "<$1>($0)"));
if (Snippet->front() == '(')
return EmptyArgs ? "()" : "($0)";
return None ? "" : (Open ? "(" : (EmptyArgs ? "()" : "($0)"));
return *Snippet; // Not an arg snippet?
}
// 'CompletionItemKind::Interface' matches template type aliases.
Expand All @@ -638,7 +647,7 @@ struct CodeCompletionBuilder {
// e.g. Foo<${1:class}>.
if (llvm::StringRef(*Snippet).ends_with("<>"))
return "<>"; // can happen with defaulted template arguments.
return "<$0>";
return None ? "" : (Open ? "<" : "<$0>");
}
return *Snippet;
}
Expand All @@ -654,7 +663,8 @@ struct CodeCompletionBuilder {
ASTContext *ASTCtx;
CodeCompletion Completion;
llvm::SmallVector<BundledEntry, 1> Bundled;
bool EnableFunctionArgSnippets;
/// the way argument lists are handled.
Config::ArgumentListsPolicy ArgumentLists;
// No snippets will be generated for using declarations and when the function
// arguments are already present.
bool IsUsingDeclaration;
Expand Down
9 changes: 5 additions & 4 deletions clang-tools-extra/clangd/CodeComplete.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include "ASTSignals.h"
#include "Compiler.h"
#include "Config.h"
#include "Protocol.h"
#include "Quality.h"
#include "index/Index.h"
Expand Down Expand Up @@ -101,17 +102,17 @@ struct CodeCompleteOptions {
/// '->' on member access etc.
bool IncludeFixIts = false;

/// Whether to generate snippets for function arguments on code-completion.
/// Needs snippets to be enabled as well.
bool EnableFunctionArgSnippets = true;

/// Whether to include index symbols that are not defined in the scopes
/// visible from the code completion point. This applies in contexts without
/// explicit scope qualifiers.
///
/// Such completions can insert scope qualifiers.
bool AllScopes = false;

/// The way argument list on calls '()' and generics '<>' are handled.
Config::ArgumentListsPolicy ArgumentLists =
Config::ArgumentListsPolicy::FullPlaceholders;

/// Whether to use the clang parser, or fallback to text-based completion
/// (using identifiers in the current file and symbol indexes).
enum CodeCompletionParse {
Expand Down
14 changes: 14 additions & 0 deletions clang-tools-extra/clangd/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,25 @@ struct Config {
std::vector<std::string> FullyQualifiedNamespaces;
} Style;

/// controls the completion options for argument lists.
enum class ArgumentListsPolicy {
/// nothing, no argument list and also NO Delimiters "()" or "<>".
None,
/// open, only opening delimiter "(" or "<".
OpenDelimiter,
/// empty pair of delimiters "()" or "<>".
Delimiters,
/// full name of both type and variable.
FullPlaceholders,
};

/// Configures code completion feature.
struct {
/// Whether code completion includes results that are not visible in current
/// scopes.
bool AllScopes = true;
/// controls the completion options for argument lists.
ArgumentListsPolicy ArgumentLists = ArgumentListsPolicy::FullPlaceholders;
} Completion;

/// Configures hover feature.
Expand Down
15 changes: 15 additions & 0 deletions clang-tools-extra/clangd/ConfigCompile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,21 @@ struct FragmentCompiler {
C.Completion.AllScopes = AllScopes;
});
}
if (F.ArgumentLists) {
if (auto Val =
compileEnum<Config::ArgumentListsPolicy>("ArgumentLists",
*F.ArgumentLists)
.map("None", Config::ArgumentListsPolicy::None)
.map("OpenDelimiter",
Config::ArgumentListsPolicy::OpenDelimiter)
.map("Delimiters", Config::ArgumentListsPolicy::Delimiters)
.map("FullPlaceholders",
Config::ArgumentListsPolicy::FullPlaceholders)
.value())
Out.Apply.push_back([Val](const Params &, Config &C) {
C.Completion.ArgumentLists = *Val;
});
}
}

void compile(Fragment::HoverBlock &&F) {
Expand Down
8 changes: 8 additions & 0 deletions clang-tools-extra/clangd/ConfigFragment.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CONFIGFRAGMENT_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CONFIGFRAGMENT_H

#include "Config.h"
#include "ConfigProvider.h"
#include "llvm/Support/SMLoc.h"
#include "llvm/Support/SourceMgr.h"
Expand Down Expand Up @@ -308,6 +309,13 @@ struct Fragment {
/// Whether code completion should include suggestions from scopes that are
/// not visible. The required scope prefix will be inserted.
std::optional<Located<bool>> AllScopes;
/// How to present the argument list between '()' and '<>':
/// valid values are enum Config::ArgumentListsPolicy values:
/// None: Nothing at all
/// OpenDelimiter: only opening delimiter "(" or "<"
/// Delimiters: empty pair of delimiters "()" or "<>"
/// FullPlaceholders: full name of both type and parameter
std::optional<Located<std::string>> ArgumentLists;
};
CompletionBlock Completion;

Expand Down
4 changes: 4 additions & 0 deletions clang-tools-extra/clangd/ConfigYAML.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,10 @@ class Parser {
if (auto AllScopes = boolValue(N, "AllScopes"))
F.AllScopes = *AllScopes;
});
Dict.handle("ArgumentLists", [&](Node &N) {
if (auto ArgumentLists = scalarValue(N, "ArgumentLists"))
F.ArgumentLists = *ArgumentLists;
});
Dict.parse(N);
}

Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/FindSymbols.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ getWorkspaceSymbols(llvm::StringRef Query, int Limit,
*Req.Limit *= 5;
}
TopN<ScoredSymbolInfo, ScoredSymbolGreater> Top(
Req.Limit ? *Req.Limit : std::numeric_limits<size_t>::max());
Req.Limit.value_or(std::numeric_limits<size_t>::max()));
FuzzyMatcher Filter(Req.Query);

Index->fuzzyFind(Req, [HintPath, &Top, &Filter, AnyScope = Req.AnyScope,
Expand Down
4 changes: 2 additions & 2 deletions clang-tools-extra/clangd/Headers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ class IncludeStructure::RecordHeaders : public PPCallbacks {
IDs.push_back(HID);
}
}
Out->MainFileIncludesBySpelling.try_emplace(Inc.Written)
.first->second.push_back(Out->MainFileIncludes.size() - 1);
Out->MainFileIncludesBySpelling[Inc.Written].push_back(
Out->MainFileIncludes.size() - 1);
}

// Record include graph (not just for main-file includes)
Expand Down
3 changes: 1 addition & 2 deletions clang-tools-extra/clangd/XRefs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2282,8 +2282,7 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
elog("incomingCalls failed to convert location: {0}", Loc.takeError());
return;
}
auto It = CallsIn.try_emplace(R.Container, std::vector<Range>{}).first;
It->second.push_back(Loc->range);
CallsIn[R.Container].push_back(Loc->range);

ContainerLookup.IDs.insert(R.Container);
});
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/index/MemIndex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ bool MemIndex::fuzzyFind(
trace::Span Tracer("MemIndex fuzzyFind");

TopN<std::pair<float, const Symbol *>> Top(
Req.Limit ? *Req.Limit : std::numeric_limits<size_t>::max());
Req.Limit.value_or(std::numeric_limits<size_t>::max()));
FuzzyMatcher Filter(Req.Query);
bool More = false;
for (const auto &Pair : Index) {
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/index/dex/Dex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ bool Dex::fuzzyFind(const FuzzyFindRequest &Req,
return LHS.second > RHS.second;
};
TopN<IDAndScore, decltype(Compare)> Top(
Req.Limit ? *Req.Limit : std::numeric_limits<size_t>::max(), Compare);
Req.Limit.value_or(std::numeric_limits<size_t>::max()), Compare);
for (const auto &IDAndScore : IDAndScores) {
const DocID SymbolDocID = IDAndScore.first;
const auto *Sym = Symbols[SymbolDocID];
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/index/remote/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ if (CLANGD_ENABLE_REMOTE)
# target-local?
add_definitions(-DGOOGLE_PROTOBUF_NO_RTTI=1)

add_clang_library(clangdRemoteIndex
add_clang_library(clangdRemoteIndex STATIC
Client.cpp

LINK_LIBS
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
add_clang_library(clangdRemoteMarshalling
add_clang_library(clangdRemoteMarshalling STATIC
Marshalling.cpp

LINK_LIBS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../../)
# When compiled without Remote Index support, the real implementation index
# client is not present. Users will get a notification about this when trying
# to connect to remote index server instance.
add_clang_library(clangdRemoteIndex
add_clang_library(clangdRemoteIndex STATIC
UnimplementedClient.cpp

LINK_LIBS
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ add_clang_library(clangDaemonTweaks OBJECT
RemoveUsingNamespace.cpp
ScopifyEnum.cpp
SpecialMembers.cpp
SwapBinaryOperands.cpp
SwapIfBranches.cpp

LINK_LIBS
Expand Down
50 changes: 44 additions & 6 deletions clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,28 @@ getFunctionSourceAfterReplacements(const FunctionDecl *FD,
SM.getBufferData(SM.getMainFileID()), Replacements);
if (!QualifiedFunc)
return QualifiedFunc.takeError();
return QualifiedFunc->substr(FuncBegin, FuncEnd - FuncBegin + 1);

std::string TemplatePrefix;
if (auto *MD = llvm::dyn_cast<CXXMethodDecl>(FD)) {
for (const CXXRecordDecl *Parent = MD->getParent(); Parent;
Parent =
llvm::dyn_cast_or_null<const CXXRecordDecl>(Parent->getParent())) {
if (const TemplateParameterList *Params =
Parent->getDescribedTemplateParams()) {
std::string S;
llvm::raw_string_ostream Stream(S);
Params->print(Stream, FD->getASTContext());
if (!S.empty())
*S.rbegin() = '\n'; // Replace space with newline
TemplatePrefix.insert(0, S);
}
}
}

auto Source = QualifiedFunc->substr(FuncBegin, FuncEnd - FuncBegin + 1);
if (!TemplatePrefix.empty())
Source.insert(0, TemplatePrefix);
return Source;
}

// Returns replacements to delete tokens with kind `Kind` in the range
Expand Down Expand Up @@ -212,9 +233,13 @@ getFunctionSourceCode(const FunctionDecl *FD, const DeclContext *TargetContext,
}
}
const NamedDecl *ND = Ref.Targets.front();
const std::string Qualifier =
std::string Qualifier =
getQualification(AST, TargetContext,
SM.getLocForStartOfFile(SM.getMainFileID()), ND);
if (ND->getDeclContext()->isDependentContext() &&
llvm::isa<TypeDecl>(ND)) {
Qualifier.insert(0, "typename ");
}
if (auto Err = DeclarationCleanups.add(
tooling::Replacement(SM, Ref.NameLoc, 0, Qualifier)))
Errors = llvm::joinErrors(std::move(Errors), std::move(Err));
Expand Down Expand Up @@ -407,10 +432,23 @@ class DefineOutline : public Tweak {
return !SameFile;
}

// Bail out in templated classes, as it is hard to spell the class name,
// i.e if the template parameter is unnamed.
if (MD->getParent()->isTemplated())
return false;
for (const CXXRecordDecl *Parent = MD->getParent(); Parent;
Parent =
llvm::dyn_cast_or_null<const CXXRecordDecl>(Parent->getParent())) {
if (const TemplateParameterList *Params =
Parent->getDescribedTemplateParams()) {

// Class template member functions must be defined in the
// same file.
SameFile = true;

// Bail out if the template parameter is unnamed.
for (NamedDecl *P : *Params) {
if (!P->getIdentifier())
return false;
}
}
}

// The refactoring is meaningless for unnamed classes and namespaces,
// unless we're outlining in the same file
Expand Down
217 changes: 217 additions & 0 deletions clang-tools-extra/clangd/refactor/tweaks/SwapBinaryOperands.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
//===--- SwapBinaryOperands.cpp ----------------------------------*- 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
//
//===----------------------------------------------------------------------===//
#include "ParsedAST.h"
#include "Protocol.h"
#include "Selection.h"
#include "SourceCode.h"
#include "refactor/Tweak.h"
#include "support/Logger.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Expr.h"
#include "clang/AST/OperationKinds.h"
#include "clang/AST/Stmt.h"
#include "clang/Basic/LLVM.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Tooling/Core/Replacement.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/FormatVariadic.h"
#include <string>
#include <utility>

namespace clang {
namespace clangd {
namespace {
/// Check whether it makes logical sense to swap operands to an operator.
/// Assignment or member access operators are rarely swappable
/// while keeping the meaning intact, whereas comparison operators, mathematical
/// operators, etc. are often desired to be swappable for readability, avoiding
/// bugs by assigning to nullptr when comparison was desired, etc.
bool isOpSwappable(const BinaryOperatorKind Opcode) {
switch (Opcode) {
case BinaryOperatorKind::BO_Mul:
case BinaryOperatorKind::BO_Add:
case BinaryOperatorKind::BO_LT:
case BinaryOperatorKind::BO_GT:
case BinaryOperatorKind::BO_LE:
case BinaryOperatorKind::BO_GE:
case BinaryOperatorKind::BO_EQ:
case BinaryOperatorKind::BO_NE:
case BinaryOperatorKind::BO_And:
case BinaryOperatorKind::BO_Xor:
case BinaryOperatorKind::BO_Or:
case BinaryOperatorKind::BO_LAnd:
case BinaryOperatorKind::BO_LOr:
case BinaryOperatorKind::BO_Comma:
return true;
// Noncommutative operators:
case BinaryOperatorKind::BO_Div:
case BinaryOperatorKind::BO_Sub:
case BinaryOperatorKind::BO_Shl:
case BinaryOperatorKind::BO_Shr:
case BinaryOperatorKind::BO_Rem:
// <=> is noncommutative
case BinaryOperatorKind::BO_Cmp:
// Member access:
case BinaryOperatorKind::BO_PtrMemD:
case BinaryOperatorKind::BO_PtrMemI:
// Assignment:
case BinaryOperatorKind::BO_Assign:
case BinaryOperatorKind::BO_MulAssign:
case BinaryOperatorKind::BO_DivAssign:
case BinaryOperatorKind::BO_RemAssign:
case BinaryOperatorKind::BO_AddAssign:
case BinaryOperatorKind::BO_SubAssign:
case BinaryOperatorKind::BO_ShlAssign:
case BinaryOperatorKind::BO_ShrAssign:
case BinaryOperatorKind::BO_AndAssign:
case BinaryOperatorKind::BO_XorAssign:
case BinaryOperatorKind::BO_OrAssign:
return false;
}
return false;
}

/// Some operators are asymmetric and need to be flipped when swapping their
/// operands
/// @param[out] Opcode the opcode to potentially swap
/// If the opcode does not need to be swapped or is not swappable, does nothing
BinaryOperatorKind swapOperator(const BinaryOperatorKind Opcode) {
switch (Opcode) {
case BinaryOperatorKind::BO_LT:
return BinaryOperatorKind::BO_GT;

case BinaryOperatorKind::BO_GT:
return BinaryOperatorKind::BO_LT;

case BinaryOperatorKind::BO_LE:
return BinaryOperatorKind::BO_GE;

case BinaryOperatorKind::BO_GE:
return BinaryOperatorKind::BO_LE;

case BinaryOperatorKind::BO_Mul:
case BinaryOperatorKind::BO_Add:
case BinaryOperatorKind::BO_Cmp:
case BinaryOperatorKind::BO_EQ:
case BinaryOperatorKind::BO_NE:
case BinaryOperatorKind::BO_And:
case BinaryOperatorKind::BO_Xor:
case BinaryOperatorKind::BO_Or:
case BinaryOperatorKind::BO_LAnd:
case BinaryOperatorKind::BO_LOr:
case BinaryOperatorKind::BO_Comma:
case BinaryOperatorKind::BO_Div:
case BinaryOperatorKind::BO_Sub:
case BinaryOperatorKind::BO_Shl:
case BinaryOperatorKind::BO_Shr:
case BinaryOperatorKind::BO_Rem:
case BinaryOperatorKind::BO_PtrMemD:
case BinaryOperatorKind::BO_PtrMemI:
case BinaryOperatorKind::BO_Assign:
case BinaryOperatorKind::BO_MulAssign:
case BinaryOperatorKind::BO_DivAssign:
case BinaryOperatorKind::BO_RemAssign:
case BinaryOperatorKind::BO_AddAssign:
case BinaryOperatorKind::BO_SubAssign:
case BinaryOperatorKind::BO_ShlAssign:
case BinaryOperatorKind::BO_ShrAssign:
case BinaryOperatorKind::BO_AndAssign:
case BinaryOperatorKind::BO_XorAssign:
case BinaryOperatorKind::BO_OrAssign:
return Opcode;
}
llvm_unreachable("Unknown BinaryOperatorKind enum");
}

/// Swaps the operands to a binary operator
/// Before:
/// x != nullptr
/// ^ ^^^^^^^
/// After:
/// nullptr != x
class SwapBinaryOperands : public Tweak {
public:
const char *id() const final;

bool prepare(const Selection &Inputs) override;
Expected<Effect> apply(const Selection &Inputs) override;
std::string title() const override {
return llvm::formatv("Swap operands to {0}",
Op ? Op->getOpcodeStr() : "binary operator");
}
llvm::StringLiteral kind() const override {
return CodeAction::REFACTOR_KIND;
}
bool hidden() const override { return false; }

private:
const BinaryOperator *Op;
};

REGISTER_TWEAK(SwapBinaryOperands)

bool SwapBinaryOperands::prepare(const Selection &Inputs) {
for (const SelectionTree::Node *N = Inputs.ASTSelection.commonAncestor();
N && !Op; N = N->Parent) {
// Stop once we hit a block, e.g. a lambda in one of the operands.
// This makes sure that the selection point is in the 'scope' of the binary
// operator, not from somewhere inside a lambda for example
// (5 < [](){ ^return 1; })
if (llvm::isa_and_nonnull<CompoundStmt>(N->ASTNode.get<Stmt>()))
return false;
Op = dyn_cast_or_null<BinaryOperator>(N->ASTNode.get<Stmt>());
// If we hit upon a nonswappable binary operator, ignore and keep going
if (Op && !isOpSwappable(Op->getOpcode())) {
Op = nullptr;
}
}
return Op != nullptr;
}

Expected<Tweak::Effect> SwapBinaryOperands::apply(const Selection &Inputs) {
const auto &Ctx = Inputs.AST->getASTContext();
const auto &SrcMgr = Inputs.AST->getSourceManager();

const auto LHSRng = toHalfOpenFileRange(SrcMgr, Ctx.getLangOpts(),
Op->getLHS()->getSourceRange());
if (!LHSRng)
return error(
"Could not obtain range of the 'lhs' of the operator. Macros?");
const auto RHSRng = toHalfOpenFileRange(SrcMgr, Ctx.getLangOpts(),
Op->getRHS()->getSourceRange());
if (!RHSRng)
return error(
"Could not obtain range of the 'rhs' of the operator. Macros?");
const auto OpRng =
toHalfOpenFileRange(SrcMgr, Ctx.getLangOpts(), Op->getOperatorLoc());
if (!OpRng)
return error("Could not obtain range of the operator itself. Macros?");

const auto LHSCode = toSourceCode(SrcMgr, *LHSRng);
const auto RHSCode = toSourceCode(SrcMgr, *RHSRng);
const auto OperatorCode = toSourceCode(SrcMgr, *OpRng);

tooling::Replacements Result;
if (auto Err = Result.add(tooling::Replacement(
Ctx.getSourceManager(), LHSRng->getBegin(), LHSCode.size(), RHSCode)))
return std::move(Err);
if (auto Err = Result.add(tooling::Replacement(
Ctx.getSourceManager(), RHSRng->getBegin(), RHSCode.size(), LHSCode)))
return std::move(Err);
const auto SwappedOperator = swapOperator(Op->getOpcode());
if (auto Err = Result.add(tooling::Replacement(
Ctx.getSourceManager(), OpRng->getBegin(), OperatorCode.size(),
Op->getOpcodeStr(SwappedOperator))))
return std::move(Err);
return Effect::mainFileEdit(SrcMgr, std::move(Result));
}

} // namespace
} // namespace clangd
} // namespace clang
Loading