1,213 changes: 1,213 additions & 0 deletions bolt/docs/CommandLineArgumentReference.md

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions bolt/include/bolt/Core/DIEBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ class DIEBuilder {
uint64_t UnitSize{0};
llvm::DenseSet<uint64_t> AllProcessed;
DWARF5AcceleratorTable &DebugNamesTable;
// Unordered map to handle name collision if output DWO directory is
// specified.
std::unordered_map<std::string, uint32_t> NameToIndexMap;

/// Returns current state of the DIEBuilder
State &getState() { return *BuilderState.get(); }
Expand Down Expand Up @@ -384,6 +387,17 @@ class DIEBuilder {
bool deleteValue(DIEValueList *Die, dwarf::Attribute Attribute) {
return Die->deleteValue(Attribute);
}
/// Updates DWO Name and Compilation directory for Skeleton CU \p Unit.
std::string updateDWONameCompDir(DebugStrOffsetsWriter &StrOffstsWriter,
DebugStrWriter &StrWriter,
DWARFUnit &SkeletonCU,
std::optional<StringRef> DwarfOutputPath,
std::optional<StringRef> DWONameToUse);
/// Updates DWO Name and Compilation directory for Type Units.
void updateDWONameCompDirForTypes(DebugStrOffsetsWriter &StrOffstsWriter,
DebugStrWriter &StrWriter, DWARFUnit &Unit,
std::optional<StringRef> DwarfOutputPath,
const StringRef DWOName);
};
} // namespace bolt
} // namespace llvm
Expand Down
14 changes: 11 additions & 3 deletions bolt/include/bolt/Core/DebugData.h
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ class DebugAddrWriterDwarf5 : public DebugAddrWriter {
using DebugStrOffsetsBufferVector = SmallVector<char, 16>;
class DebugStrOffsetsWriter {
public:
DebugStrOffsetsWriter() {
DebugStrOffsetsWriter(BinaryContext &BC) : BC(BC) {
StrOffsetsBuffer = std::make_unique<DebugStrOffsetsBufferVector>();
StrOffsetsStream = std::make_unique<raw_svector_ostream>(*StrOffsetsBuffer);
}
Expand Down Expand Up @@ -460,20 +460,27 @@ class DebugStrOffsetsWriter {
StrOffsets.clear();
}

bool isStrOffsetsSectionModified() const {
return StrOffsetSectionWasModified;
}

private:
std::unique_ptr<DebugStrOffsetsBufferVector> StrOffsetsBuffer;
std::unique_ptr<raw_svector_ostream> StrOffsetsStream;
std::map<uint32_t, uint32_t> IndexToAddressMap;
SmallVector<uint32_t, 5> StrOffsets;
std::unordered_map<uint64_t, uint64_t> ProcessedBaseOffsets;
bool StrOffsetSectionWasModified = false;
BinaryContext &BC;
};

using DebugStrBufferVector = SmallVector<char, 16>;
class DebugStrWriter {
public:
DebugStrWriter() = delete;
DebugStrWriter(BinaryContext &BC) : BC(BC) { create(); }
DebugStrWriter(DWARFContext &DwCtx, bool IsDWO) : DwCtx(DwCtx), IsDWO(IsDWO) {
create();
}
std::unique_ptr<DebugStrBufferVector> releaseBuffer() {
return std::move(StrBuffer);
}
Expand All @@ -495,7 +502,8 @@ class DebugStrWriter {
void create();
std::unique_ptr<DebugStrBufferVector> StrBuffer;
std::unique_ptr<raw_svector_ostream> StrStream;
BinaryContext &BC;
DWARFContext &DwCtx;
bool IsDWO;
};

enum class LocWriterKind { DebugLocWriter, DebugLoclistWriter };
Expand Down
4 changes: 2 additions & 2 deletions bolt/include/bolt/Profile/DataAggregator.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,14 +122,14 @@ class DataAggregator : public DataReader {
uint64_t ExternCount{0};
};

struct BranchInfo {
struct TakenBranchInfo {
uint64_t TakenCount{0};
uint64_t MispredCount{0};
};

/// Intermediate storage for profile data. We save the results of parsing
/// and use them later for processing and assigning profile.
std::unordered_map<Trace, BranchInfo, TraceHash> BranchLBRs;
std::unordered_map<Trace, TakenBranchInfo, TraceHash> BranchLBRs;
std::unordered_map<Trace, FTInfo, TraceHash> FallthroughLBRs;
std::vector<AggregatedLBREntry> AggregatedLBRs;
std::unordered_map<uint64_t, uint64_t> BasicSamples;
Expand Down
8 changes: 6 additions & 2 deletions bolt/include/bolt/Rewrite/DWARFRewriter.h
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,16 @@ class DWARFRewriter {
using OverriddenSectionsMap = std::unordered_map<DWARFSectionKind, StringRef>;
/// Output .dwo files.
void writeDWOFiles(DWARFUnit &, const OverriddenSectionsMap &,
const std::string &, DebugLocWriter &);
const std::string &, DebugLocWriter &,
DebugStrOffsetsWriter &, DebugStrWriter &);
using KnownSectionsEntry = std::pair<MCSection *, DWARFSectionKind>;
struct DWPState {
std::unique_ptr<ToolOutputFile> Out;
std::unique_ptr<BinaryContext> TmpBC;
std::unique_ptr<MCStreamer> Streamer;
std::unique_ptr<DWPStringPool> Strings;
/// Used to store String sections for .dwo files if they are being modified.
std::vector<std::unique_ptr<DebugBufferVector>> StrSections;
const MCObjectFileInfo *MCOFI = nullptr;
const DWARFUnitIndex *CUIndex = nullptr;
std::deque<SmallString<32>> UncompressedSections;
Expand All @@ -230,7 +233,8 @@ class DWARFRewriter {

/// add content of dwo to .dwp file.
void updateDWP(DWARFUnit &, const OverriddenSectionsMap &, const UnitMeta &,
UnitMetaVectorType &, DWPState &, DebugLocWriter &);
UnitMetaVectorType &, DWPState &, DebugLocWriter &,
DebugStrOffsetsWriter &, DebugStrWriter &);
};

} // namespace bolt
Expand Down
9 changes: 4 additions & 5 deletions bolt/lib/Core/BinaryBasicBlock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,10 @@ bool BinaryBasicBlock::validateSuccessorInvariants() {
break;
}
case 2:
Valid = (CondBranch &&
(TBB == getConditionalSuccessor(true)->getLabel() &&
((!UncondBranch && !FBB) ||
(UncondBranch &&
FBB == getConditionalSuccessor(false)->getLabel()))));
Valid =
CondBranch && TBB == getConditionalSuccessor(true)->getLabel() &&
(UncondBranch ? FBB == getConditionalSuccessor(false)->getLabel()
: !FBB);
break;
}
}
Expand Down
1 change: 0 additions & 1 deletion bolt/lib/Core/BinaryEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,6 @@ void BinaryEmitter::emitFunctionBody(BinaryFunction &BF, FunctionFragment &FF,
// This assumes the second instruction in the macro-op pair will get
// assigned to its own MCRelaxableFragment. Since all JCC instructions
// are relaxable, we should be safe.
Streamer.emitNeverAlignCodeAtEnd(/*Alignment to avoid=*/64, *BC.STI);
}

if (!EmitCodeOnly) {
Expand Down
9 changes: 3 additions & 6 deletions bolt/lib/Core/BinaryFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3252,12 +3252,9 @@ bool BinaryFunction::validateCFG() const {
if (CurrentState == State::CFG_Finalized)
return true;

bool Valid = true;
for (BinaryBasicBlock *BB : BasicBlocks)
Valid &= BB->validateSuccessorInvariants();

if (!Valid)
return Valid;
if (!BB->validateSuccessorInvariants())
return false;

// Make sure all blocks in CFG are valid.
auto validateBlock = [this](const BinaryBasicBlock *BB, StringRef Desc) {
Expand Down Expand Up @@ -3326,7 +3323,7 @@ bool BinaryFunction::validateCFG() const {
}
}

return Valid;
return true;
}

void BinaryFunction::fixBranches() {
Expand Down
85 changes: 85 additions & 0 deletions bolt/lib/Core/DIEBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "llvm/Support/Casting.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/LEB128.h"

#include <algorithm>
Expand All @@ -41,6 +42,90 @@ extern cl::opt<unsigned> Verbosity;
namespace llvm {
namespace bolt {

/// Returns DWO Name to be used to update DW_AT_dwo_name/DW_AT_GNU_dwo_name
/// either in CU or TU unit die. Handles case where user specifies output DWO
/// directory, and there are duplicate names. Assumes DWO ID is unique.
static std::string
getDWOName(llvm::DWARFUnit &CU,
std::unordered_map<std::string, uint32_t> &NameToIndexMap,
std::optional<StringRef> &DwarfOutputPath) {
assert(CU.getDWOId() && "DWO ID not found.");
std::string DWOName = dwarf::toString(
CU.getUnitDIE().find({dwarf::DW_AT_dwo_name, dwarf::DW_AT_GNU_dwo_name}),
"");
assert(!DWOName.empty() &&
"DW_AT_dwo_name/DW_AT_GNU_dwo_name does not exist.");
if (DwarfOutputPath) {
DWOName = std::string(sys::path::filename(DWOName));
auto Iter = NameToIndexMap.find(DWOName);
if (Iter == NameToIndexMap.end())
Iter = NameToIndexMap.insert({DWOName, 0}).first;
DWOName.append(std::to_string(Iter->second));
++Iter->second;
}
DWOName.append(".dwo");
return DWOName;
}

/// Adds a \p Str to .debug_str section.
/// Uses \p AttrInfoVal to either update entry in a DIE for legacy DWARF using
/// \p DebugInfoPatcher, or for DWARF5 update an index in .debug_str_offsets
/// for this contribution of \p Unit.
static void addStringHelper(DebugStrOffsetsWriter &StrOffstsWriter,
DebugStrWriter &StrWriter, DIEBuilder &DIEBldr,
DIE &Die, const DWARFUnit &Unit,
DIEValue &DIEAttrInfo, StringRef Str) {
uint32_t NewOffset = StrWriter.addString(Str);
if (Unit.getVersion() >= 5) {
StrOffstsWriter.updateAddressMap(DIEAttrInfo.getDIEInteger().getValue(),
NewOffset);
return;
}
DIEBldr.replaceValue(&Die, DIEAttrInfo.getAttribute(), DIEAttrInfo.getForm(),
DIEInteger(NewOffset));
}

std::string DIEBuilder::updateDWONameCompDir(
DebugStrOffsetsWriter &StrOffstsWriter, DebugStrWriter &StrWriter,
DWARFUnit &SkeletonCU, std::optional<StringRef> DwarfOutputPath,
std::optional<StringRef> DWONameToUse) {
DIE &UnitDIE = *getUnitDIEbyUnit(SkeletonCU);
DIEValue DWONameAttrInfo = UnitDIE.findAttribute(dwarf::DW_AT_dwo_name);
if (!DWONameAttrInfo)
DWONameAttrInfo = UnitDIE.findAttribute(dwarf::DW_AT_GNU_dwo_name);
if (!DWONameAttrInfo)
return "";
std::string ObjectName;
if (DWONameToUse)
ObjectName = *DWONameToUse;
else
ObjectName = getDWOName(SkeletonCU, NameToIndexMap, DwarfOutputPath);
addStringHelper(StrOffstsWriter, StrWriter, *this, UnitDIE, SkeletonCU,
DWONameAttrInfo, ObjectName);

DIEValue CompDirAttrInfo = UnitDIE.findAttribute(dwarf::DW_AT_comp_dir);
assert(CompDirAttrInfo && "DW_AT_comp_dir is not in Skeleton CU.");

if (DwarfOutputPath) {
if (!sys::fs::exists(*DwarfOutputPath))
sys::fs::create_directory(*DwarfOutputPath);
addStringHelper(StrOffstsWriter, StrWriter, *this, UnitDIE, SkeletonCU,
CompDirAttrInfo, *DwarfOutputPath);
}
return ObjectName;
}

void DIEBuilder::updateDWONameCompDirForTypes(
DebugStrOffsetsWriter &StrOffstsWriter, DebugStrWriter &StrWriter,
DWARFUnit &Unit, std::optional<StringRef> DwarfOutputPath,
const StringRef DWOName) {
for (DWARFUnit *DU : getState().DWARF5TUVector)
updateDWONameCompDir(StrOffstsWriter, StrWriter, *DU, DwarfOutputPath,
DWOName);
if (StrOffstsWriter.isStrOffsetsSectionModified())
StrOffstsWriter.finalizeSection(Unit, *this);
}

void DIEBuilder::updateReferences() {
for (auto &[SrcDIEInfo, ReferenceInfo] : getState().AddrReferences) {
DIEInfo *DstDIEInfo = ReferenceInfo.Dst;
Expand Down
20 changes: 16 additions & 4 deletions bolt/lib/Core/DebugData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
#include <unordered_map>
#include <vector>

Expand Down Expand Up @@ -867,10 +868,17 @@ void DebugStrOffsetsWriter::finalizeSection(DWARFUnit &Unit,
DIEBuilder &DIEBldr) {
std::optional<AttrInfo> AttrVal =
findAttributeInfo(Unit.getUnitDIE(), dwarf::DW_AT_str_offsets_base);
if (!AttrVal)
if (!AttrVal && !Unit.isDWOUnit())
return;
std::optional<uint64_t> Val = AttrVal->V.getAsSectionOffset();
assert(Val && "DW_AT_str_offsets_base Value not present.");
std::optional<uint64_t> Val = std::nullopt;
if (AttrVal) {
Val = AttrVal->V.getAsSectionOffset();
} else {
if (!Unit.isDWOUnit())
BC.errs() << "BOLT-WARNING: [internal-dwarf-error]: "
"DW_AT_str_offsets_base Value not present\n";
Val = 0;
}
DIE &Die = *DIEBldr.getUnitDIEbyUnit(Unit);
DIEValue StrListBaseAttrInfo =
Die.findAttribute(dwarf::DW_AT_str_offsets_base);
Expand Down Expand Up @@ -915,7 +923,11 @@ void DebugStrWriter::create() {
}

void DebugStrWriter::initialize() {
auto StrSection = BC.DwCtx->getDWARFObj().getStrSection();
StringRef StrSection;
if (IsDWO)
StrSection = DwCtx.getDWARFObj().getStrDWOSection();
else
StrSection = DwCtx.getDWARFObj().getStrSection();
(*StrStream) << StrSection;
}

Expand Down
11 changes: 6 additions & 5 deletions bolt/lib/Passes/ValidateMemRefs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ bool ValidateMemRefs::checkAndFixJTReference(BinaryFunction &BF, MCInst &Inst,
if (!BD)
return false;

JumpTable *JT = BC.getJumpTableContainingAddress(BD->getAddress());
const uint64_t TargetAddress = BD->getAddress() + Offset;
JumpTable *JT = BC.getJumpTableContainingAddress(TargetAddress);
if (!JT)
return false;

Expand All @@ -40,10 +41,10 @@ bool ValidateMemRefs::checkAndFixJTReference(BinaryFunction &BF, MCInst &Inst,
// Accessing a jump table in another function. This is not a
// legitimate jump table access, we need to replace the reference to
// the jump table label with a regular rodata reference. Get a
// non-JT reference by fetching the symbol 1 byte before the JT label.
MCSymbol *NewSym = BC.getOrCreateGlobalSymbol(BD->getAddress() - 1, "DATAat");
BC.MIB->setOperandToSymbolRef(Inst, OperandNum, NewSym, Offset + 1, &*BC.Ctx,
0);
// non-JT reference by fetching the symbol 1 byte before the JT
// label.
MCSymbol *NewSym = BC.getOrCreateGlobalSymbol(TargetAddress - 1, "DATAat");
BC.MIB->setOperandToSymbolRef(Inst, OperandNum, NewSym, 1, &*BC.Ctx, 0);
LLVM_DEBUG(dbgs() << "BOLT-DEBUG: replaced reference @" << BF.getPrintName()
<< " from " << BD->getName() << " to " << NewSym->getName()
<< " + 1\n");
Expand Down
16 changes: 8 additions & 8 deletions bolt/lib/Profile/DataAggregator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1464,7 +1464,7 @@ uint64_t DataAggregator::parseLBRSample(const PerfBranchSample &Sample,
uint64_t To = getBinaryFunctionContainingAddress(LBR.To) ? LBR.To : 0;
if (!From && !To)
continue;
BranchInfo &Info = BranchLBRs[Trace(From, To)];
TakenBranchInfo &Info = BranchLBRs[Trace(From, To)];
++Info.TakenCount;
Info.MispredCount += LBR.Mispred;
}
Expand Down Expand Up @@ -1609,7 +1609,7 @@ void DataAggregator::processBranchEvents() {

for (const auto &AggrLBR : BranchLBRs) {
const Trace &Loc = AggrLBR.first;
const BranchInfo &Info = AggrLBR.second;
const TakenBranchInfo &Info = AggrLBR.second;
doBranch(Loc.From, Loc.To, Info.TakenCount, Info.MispredCount);
}
}
Expand Down Expand Up @@ -2000,7 +2000,7 @@ std::error_code DataAggregator::parseMMapEvents() {
std::pair<StringRef, MMapInfo> FileMMapInfo = FileMMapInfoRes.get();
if (FileMMapInfo.second.PID == -1)
continue;
if (FileMMapInfo.first.equals("(deleted)"))
if (FileMMapInfo.first == "(deleted)")
continue;

// Consider only the first mapping of the file for any given PID
Expand Down Expand Up @@ -2253,13 +2253,13 @@ DataAggregator::writeAggregatedFile(StringRef OutputFilename) const {
} else {
for (const auto &KV : NamesToBranches) {
const FuncBranchData &FBD = KV.second;
for (const llvm::bolt::BranchInfo &BI : FBD.Data) {
for (const BranchInfo &BI : FBD.Data) {
writeLocation(BI.From);
writeLocation(BI.To);
OutFile << BI.Mispreds << " " << BI.Branches << "\n";
++BranchValues;
}
for (const llvm::bolt::BranchInfo &BI : FBD.EntryData) {
for (const BranchInfo &BI : FBD.EntryData) {
// Do not output if source is a known symbol, since this was already
// accounted for in the source function
if (BI.From.IsSymbol)
Expand Down Expand Up @@ -2359,14 +2359,14 @@ std::error_code DataAggregator::writeBATYAML(BinaryContext &BC,
auto getBlock = [&BlockMap](uint32_t Offset) {
auto BlockIt = BlockMap.upper_bound(Offset);
if (LLVM_UNLIKELY(BlockIt == BlockMap.begin())) {
errs() << "BOLT-ERROR: Invalid BAT section";
errs() << "BOLT-ERROR: invalid BAT section\n";
exit(1);
}
--BlockIt;
return std::pair(BlockIt->first, BlockIt->second.getBBIndex());
};

for (const llvm::bolt::BranchInfo &BI : Branches.Data) {
for (const BranchInfo &BI : Branches.Data) {
using namespace yaml::bolt;
const auto &[BlockOffset, BlockIndex] = getBlock(BI.From.Offset);
BinaryBasicBlockProfile &YamlBB = YamlBF.Blocks[BlockIndex];
Expand All @@ -2388,7 +2388,7 @@ std::error_code DataAggregator::writeBATYAML(BinaryContext &BC,
}
}
// Set entry counts, similar to DataReader::readProfile.
for (const llvm::bolt::BranchInfo &BI : Branches.EntryData) {
for (const BranchInfo &BI : Branches.EntryData) {
if (!BlockMap.isInputBlock(BI.To.Offset)) {
if (opts::Verbosity >= 1)
errs() << "BOLT-WARNING: Unexpected EntryData in " << FuncName
Expand Down
4 changes: 2 additions & 2 deletions bolt/lib/Profile/DataReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,7 @@ bool DataReader::recordBranch(BinaryFunction &BF, uint64_t From, uint64_t To,
if (collectedInBoltedBinary() && FromBB == ToBB)
return true;

// Allow passthrough blocks.
BinaryBasicBlock *FTSuccessor = FromBB->getConditionalSuccessor(false);
if (FTSuccessor && FTSuccessor->succ_size() == 1 &&
FTSuccessor->getSuccessor(ToBB->getLabel())) {
Expand Down Expand Up @@ -1205,8 +1206,7 @@ std::error_code DataReader::parse() {

// Add entry data for branches to another function or branches
// to entry points (including recursive calls)
if (BI.To.IsSymbol &&
(!BI.From.Name.equals(BI.To.Name) || BI.To.Offset == 0)) {
if (BI.To.IsSymbol && (BI.From.Name != BI.To.Name || BI.To.Offset == 0)) {
I = GetOrCreateFuncEntry(BI.To.Name);
I->second.EntryData.emplace_back(std::move(BI));
}
Expand Down
3 changes: 1 addition & 2 deletions bolt/lib/Profile/YAMLProfileReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,7 @@ bool YAMLProfileReader::parseFunctionProfile(

BinaryBasicBlock *ToBB = Order[YamlSI.Index];
if (!BB.getSuccessor(ToBB->getLabel())) {
// Allow for BOLT-removed passthrough blocks to align with DataReader
// behavior.
// Allow passthrough blocks.
BinaryBasicBlock *FTSuccessor = BB.getConditionalSuccessor(false);
if (FTSuccessor && FTSuccessor->succ_size() == 1 &&
FTSuccessor->getSuccessor(ToBB->getLabel())) {
Expand Down
1 change: 1 addition & 0 deletions bolt/lib/Rewrite/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
set(LLVM_LINK_COMPONENTS
Core
DebugInfoDWARF
DWP
JITLink
Expand Down
169 changes: 64 additions & 105 deletions bolt/lib/Rewrite/DWARFRewriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -458,32 +458,6 @@ static std::optional<uint64_t> getAsAddress(const DWARFUnit &DU,
return std::nullopt;
}

/// Returns DWO Name to be used. Handles case where user specifies output DWO
/// directory, and there are duplicate names. Assumes DWO ID is unique.
static std::string
getDWOName(llvm::DWARFUnit &CU,
std::unordered_map<std::string, uint32_t> &NameToIndexMap) {
std::optional<uint64_t> DWOId = CU.getDWOId();
assert(DWOId && "DWO ID not found.");
(void)DWOId;

std::string DWOName = dwarf::toString(
CU.getUnitDIE().find({dwarf::DW_AT_dwo_name, dwarf::DW_AT_GNU_dwo_name}),
"");
assert(!DWOName.empty() &&
"DW_AT_dwo_name/DW_AT_GNU_dwo_name does not exists.");
if (!opts::DwarfOutputPath.empty()) {
DWOName = std::string(sys::path::filename(DWOName));
auto Iter = NameToIndexMap.find(DWOName);
if (Iter == NameToIndexMap.end())
Iter = NameToIndexMap.insert({DWOName, 0}).first;
DWOName.append(std::to_string(Iter->second));
++Iter->second;
}
DWOName.append(".dwo");
return DWOName;
}

static std::unique_ptr<DIEStreamer>
createDIEStreamer(const Triple &TheTriple, raw_pwrite_stream &OutFile,
StringRef Swift5ReflectionSegmentName, DIEBuilder &DIEBldr,
Expand Down Expand Up @@ -515,7 +489,9 @@ static void emitDWOBuilder(const std::string &DWOName,
DIEBuilder &DWODIEBuilder, DWARFRewriter &Rewriter,
DWARFUnit &SplitCU, DWARFUnit &CU,
DWARFRewriter::DWPState &State,
DebugLocWriter &LocWriter) {
DebugLocWriter &LocWriter,
DebugStrOffsetsWriter &StrOffstsWriter,
DebugStrWriter &StrWriter) {
// Populate debug_info and debug_abbrev for current dwo into StringRef.
DWODIEBuilder.generateAbbrevs();
DWODIEBuilder.finish();
Expand Down Expand Up @@ -577,54 +553,10 @@ static void emitDWOBuilder(const std::string &DWOName,
}
if (opts::WriteDWP)
Rewriter.updateDWP(CU, OverriddenSections, CUMI, TUMetaVector, State,
LocWriter);
LocWriter, StrOffstsWriter, StrWriter);
else
Rewriter.writeDWOFiles(CU, OverriddenSections, DWOName, LocWriter);
}

/// Adds a \p Str to .debug_str section.
/// Uses \p AttrInfoVal to either update entry in a DIE for legacy DWARF using
/// \p DebugInfoPatcher, or for DWARF5 update an index in .debug_str_offsets
/// for this contribution of \p Unit.
static void addStringHelper(DebugStrOffsetsWriter &StrOffstsWriter,
DebugStrWriter &StrWriter, DIEBuilder &DIEBldr,
DIE &Die, const DWARFUnit &Unit,
DIEValue &DIEAttrInfo, StringRef Str) {
uint32_t NewOffset = StrWriter.addString(Str);
if (Unit.getVersion() >= 5) {
StrOffstsWriter.updateAddressMap(DIEAttrInfo.getDIEInteger().getValue(),
NewOffset);
return;
}
DIEBldr.replaceValue(&Die, DIEAttrInfo.getAttribute(), DIEAttrInfo.getForm(),
DIEInteger(NewOffset));
}

static std::string
updateDWONameCompDir(DebugStrOffsetsWriter &StrOffstsWriter,
DebugStrWriter &StrWriter,
std::unordered_map<std::string, uint32_t> &NameToIndexMap,
DWARFUnit &Unit, DIEBuilder &DIEBldr, DIE &UnitDIE) {
DIEValue DWONameAttrInfo = UnitDIE.findAttribute(dwarf::DW_AT_dwo_name);
if (!DWONameAttrInfo)
DWONameAttrInfo = UnitDIE.findAttribute(dwarf::DW_AT_GNU_dwo_name);
assert(DWONameAttrInfo && "DW_AT_dwo_name is not in Skeleton CU.");
std::string ObjectName;

ObjectName = getDWOName(Unit, NameToIndexMap);
addStringHelper(StrOffstsWriter, StrWriter, DIEBldr, UnitDIE, Unit,
DWONameAttrInfo, ObjectName.c_str());

DIEValue CompDirAttrInfo = UnitDIE.findAttribute(dwarf::DW_AT_comp_dir);
assert(CompDirAttrInfo && "DW_AT_comp_dir is not in Skeleton CU.");

if (!opts::DwarfOutputPath.empty()) {
if (!sys::fs::exists(opts::DwarfOutputPath))
sys::fs::create_directory(opts::DwarfOutputPath);
addStringHelper(StrOffstsWriter, StrWriter, DIEBldr, UnitDIE, Unit,
CompDirAttrInfo, opts::DwarfOutputPath.c_str());
}
return ObjectName;
Rewriter.writeDWOFiles(CU, OverriddenSections, DWOName, LocWriter,
StrOffstsWriter, StrWriter);
}

using DWARFUnitVec = std::vector<DWARFUnit *>;
Expand Down Expand Up @@ -673,9 +605,8 @@ void DWARFRewriter::updateDebugInfo() {
return;

ARangesSectionWriter = std::make_unique<DebugARangesSectionWriter>();
StrWriter = std::make_unique<DebugStrWriter>(BC);

StrOffstsWriter = std::make_unique<DebugStrOffsetsWriter>();
StrWriter = std::make_unique<DebugStrWriter>(*BC.DwCtx, false);
StrOffstsWriter = std::make_unique<DebugStrOffsetsWriter>(BC);

if (!opts::DeterministicDebugInfo) {
opts::DeterministicDebugInfo = true;
Expand Down Expand Up @@ -720,10 +651,6 @@ void DWARFRewriter::updateDebugInfo() {
return LocListWritersByCU[CUIndex++].get();
};

// Unordered maps to handle name collision if output DWO directory is
// specified.
std::unordered_map<std::string, uint32_t> NameToIndexMap;

DWARF5AcceleratorTable DebugNamesTable(opts::CreateDebugNames, BC,
*StrWriter);
DWPState State;
Expand All @@ -747,13 +674,20 @@ void DWARFRewriter::updateDebugInfo() {
Unit);
DWODIEBuilder.buildDWOUnit(**SplitCU);
std::string DWOName = "";
std::optional<std::string> DwarfOutputPath =
opts::DwarfOutputPath.empty()
? std::nullopt
: std::optional<std::string>(opts::DwarfOutputPath.c_str());
{
std::lock_guard<std::mutex> Lock(AccessMutex);
DWOName = updateDWONameCompDir(*StrOffstsWriter, *StrWriter,
NameToIndexMap, *Unit, *DIEBlder,
*DIEBlder->getUnitDIEbyUnit(*Unit));
DWOName = DIEBlder->updateDWONameCompDir(
*StrOffstsWriter, *StrWriter, *Unit, DwarfOutputPath, std::nullopt);
}

DebugStrOffsetsWriter DWOStrOffstsWriter(BC);
DebugStrWriter DWOStrWriter((*SplitCU)->getContext(), true);
DWODIEBuilder.updateDWONameCompDirForTypes(DWOStrOffstsWriter,
DWOStrWriter, **SplitCU,
DwarfOutputPath, DWOName);
DebugLoclistWriter DebugLocDWoWriter(*Unit, Unit->getVersion(), true);
DebugRangesSectionWriter *TempRangesSectionWriter = RangesSectionWriter;
if (Unit->getVersion() >= 5) {
Expand All @@ -771,7 +705,7 @@ void DWARFRewriter::updateDebugInfo() {
TempRangesSectionWriter->finalizeSection();

emitDWOBuilder(DWOName, DWODIEBuilder, *this, **SplitCU, *Unit, State,
DebugLocDWoWriter);
DebugLocDWoWriter, DWOStrOffstsWriter, DWOStrWriter);
}

if (Unit->getVersion() >= 5) {
Expand Down Expand Up @@ -1550,7 +1484,7 @@ CUOffsetMap DWARFRewriter::finalizeTypeSections(DIEBuilder &DIEBlder,
for (const SectionRef &Section : Obj->sections()) {
StringRef Contents = cantFail(Section.getContents());
StringRef Name = cantFail(Section.getName());
if (Name.equals(".debug_types"))
if (Name == ".debug_types")
BC.registerOrUpdateNoteSection(".debug_types", copyByteArray(Contents),
Contents.size());
}
Expand Down Expand Up @@ -1633,10 +1567,10 @@ void DWARFRewriter::finalizeDebugSections(
for (const SectionRef &Secs : Obj->sections()) {
StringRef Contents = cantFail(Secs.getContents());
StringRef Name = cantFail(Secs.getName());
if (Name.equals(".debug_abbrev")) {
if (Name == ".debug_abbrev") {
BC.registerOrUpdateNoteSection(".debug_abbrev", copyByteArray(Contents),
Contents.size());
} else if (Name.equals(".debug_info")) {
} else if (Name == ".debug_info") {
BC.registerOrUpdateNoteSection(".debug_info", copyByteArray(Contents),
Contents.size());
}
Expand Down Expand Up @@ -1736,6 +1670,7 @@ std::optional<StringRef> updateDebugData(
const DWARFUnitIndex::Entry *CUDWOEntry, uint64_t DWOId,
std::unique_ptr<DebugBufferVector> &OutputBuffer,
DebugRangeListsSectionWriter *RangeListsWriter, DebugLocWriter &LocWriter,
DebugStrOffsetsWriter &StrOffstsWriter, DebugStrWriter &StrWriter,
const llvm::bolt::DWARFRewriter::OverriddenSectionsMap &OverridenSections) {

using DWOSectionContribution =
Expand Down Expand Up @@ -1771,9 +1706,14 @@ std::optional<StringRef> updateDebugData(
};
switch (SectionIter->second.second) {
default: {
if (!SectionName.equals("debug_str.dwo"))
if (SectionName != "debug_str.dwo")
errs() << "BOLT-WARNING: unsupported debug section: " << SectionName
<< "\n";
if (StrWriter.isInitialized()) {
OutputBuffer = StrWriter.releaseBuffer();
return StringRef(reinterpret_cast<const char *>(OutputBuffer->data()),
OutputBuffer->size());
}
return SectionContents;
}
case DWARFSectionKind::DW_SECT_INFO: {
Expand All @@ -1783,6 +1723,11 @@ std::optional<StringRef> updateDebugData(
return getOverridenSection(DWARFSectionKind::DW_SECT_EXT_TYPES);
}
case DWARFSectionKind::DW_SECT_STR_OFFSETS: {
if (StrOffstsWriter.isFinalized()) {
OutputBuffer = StrOffstsWriter.releaseBuffer();
return StringRef(reinterpret_cast<const char *>(OutputBuffer->data()),
OutputBuffer->size());
}
return getSliceData(CUDWOEntry, SectionContents,
DWARFSectionKind::DW_SECT_STR_OFFSETS, DWPOffset);
}
Expand Down Expand Up @@ -1884,7 +1829,9 @@ void DWARFRewriter::updateDWP(DWARFUnit &CU,
const OverriddenSectionsMap &OverridenSections,
const DWARFRewriter::UnitMeta &CUMI,
DWARFRewriter::UnitMetaVectorType &TUMetaVector,
DWPState &State, DebugLocWriter &LocWriter) {
DWPState &State, DebugLocWriter &LocWriter,
DebugStrOffsetsWriter &StrOffstsWriter,
DebugStrWriter &StrWriter) {
const uint64_t DWOId = *CU.getDWOId();
MCSection *const StrOffsetSection = State.MCOFI->getDwarfStrOffDWOSection();
assert(StrOffsetSection && "StrOffsetSection does not exist.");
Expand Down Expand Up @@ -1941,15 +1888,18 @@ void DWARFRewriter::updateDWP(DWARFUnit &CU,
TUEntry.Contributions[Index].getLength32();
State.TypeIndexEntries.insert(std::make_pair(Hash, TUEntry));
};
std::unique_ptr<DebugBufferVector> StrOffsetsOutputData;
std::unique_ptr<DebugBufferVector> StrOutputData;
for (const SectionRef &Section : DWOFile->sections()) {
std::unique_ptr<DebugBufferVector> OutputData;
std::unique_ptr<DebugBufferVector> OutputData = nullptr;
StringRef SectionName = getSectionName(Section);
Expected<StringRef> ContentsExp = Section.getContents();
assert(ContentsExp && "Invalid contents.");
std::optional<StringRef> TOutData = updateDebugData(
(*DWOCU)->getContext(), SectionName, *ContentsExp, State.KnownSections,
*State.Streamer, *this, CUDWOEntry, DWOId, OutputData,
RangeListssWriter, LocWriter, OverridenSections);
std::optional<StringRef> TOutData =
updateDebugData((*DWOCU)->getContext(), SectionName, *ContentsExp,
State.KnownSections, *State.Streamer, *this, CUDWOEntry,
DWOId, OutputData, RangeListssWriter, LocWriter,
StrOffstsWriter, StrWriter, OverridenSections);
if (!TOutData)
continue;

Expand All @@ -1959,16 +1909,19 @@ void DWARFRewriter::updateDWP(DWARFUnit &CU,
continue;
}

if (SectionName.equals("debug_str.dwo")) {
if (SectionName == "debug_str.dwo") {
CurStrSection = OutData;
StrOutputData = std::move(OutputData);
} else {
// Since handleDebugDataPatching returned true, we already know this is
// a known section.
auto SectionIter = State.KnownSections.find(SectionName);
if (SectionIter->second.second == DWARFSectionKind::DW_SECT_STR_OFFSETS)
if (SectionIter->second.second == DWARFSectionKind::DW_SECT_STR_OFFSETS) {
CurStrOffsetSection = OutData;
else
StrOffsetsOutputData = std::move(OutputData);
} else {
State.Streamer->emitBytes(OutData);
}
unsigned int Index =
getContributionIndex(SectionIter->second.second, State.IndexVersion);
uint64_t Offset = State.ContributionOffsets[Index];
Expand All @@ -1992,6 +1945,10 @@ void DWARFRewriter::updateDWP(DWARFUnit &CU,
// based on hash.
if (!StrSectionWrittenOut && !CurStrOffsetSection.empty() &&
!CurStrSection.empty()) {
// If debug_str.dwo section was modified storing it until dwp is written
// out. DWPStringPool stores raw pointers to strings.
if (StrOutputData)
State.StrSections.push_back(std::move(StrOutputData));
writeStringsAndOffsets(*State.Streamer.get(), *State.Strings.get(),
StrOffsetSection, CurStrSection,
CurStrOffsetSection, CU.getVersion());
Expand All @@ -2017,7 +1974,8 @@ void DWARFRewriter::updateDWP(DWARFUnit &CU,

void DWARFRewriter::writeDWOFiles(
DWARFUnit &CU, const OverriddenSectionsMap &OverridenSections,
const std::string &DWOName, DebugLocWriter &LocWriter) {
const std::string &DWOName, DebugLocWriter &LocWriter,
DebugStrOffsetsWriter &StrOffstsWriter, DebugStrWriter &StrWriter) {
// Setup DWP code once.
DWARFContext *DWOCtx = BC.getDWOContext();
const uint64_t DWOId = *CU.getDWOId();
Expand Down Expand Up @@ -2072,10 +2030,11 @@ void DWARFRewriter::writeDWOFiles(
// have .debug_rnglists so won't be part of the loop below.
if (!RangeListssWriter->empty()) {
std::unique_ptr<DebugBufferVector> OutputData;
if (std::optional<StringRef> OutData = updateDebugData(
(*DWOCU)->getContext(), "debug_rnglists.dwo", "", KnownSections,
*Streamer, *this, CUDWOEntry, DWOId, OutputData,
RangeListssWriter, LocWriter, OverridenSections))
if (std::optional<StringRef> OutData =
updateDebugData((*DWOCU)->getContext(), "debug_rnglists.dwo", "",
KnownSections, *Streamer, *this, CUDWOEntry,
DWOId, OutputData, RangeListssWriter, LocWriter,
StrOffstsWriter, StrWriter, OverridenSections))
Streamer->emitBytes(*OutData);
}
}
Expand All @@ -2090,7 +2049,7 @@ void DWARFRewriter::writeDWOFiles(
if (std::optional<StringRef> OutData = updateDebugData(
(*DWOCU)->getContext(), SectionName, *ContentsExp, KnownSections,
*Streamer, *this, CUDWOEntry, DWOId, OutputData, RangeListssWriter,
LocWriter, OverridenSections))
LocWriter, StrOffstsWriter, StrWriter, OverridenSections))
Streamer->emitBytes(*OutData);
}
Streamer->finish();
Expand Down
2 changes: 1 addition & 1 deletion bolt/lib/Rewrite/SDTRewriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ void SDTRewriter::readSection() {

StringRef Name = DE.getCStr(&Offset);

if (!Name.equals("stapsdt"))
if (Name != "stapsdt")
errs() << "BOLT-WARNING: SDT note name \"" << Name
<< "\" is not expected\n";

Expand Down
22 changes: 11 additions & 11 deletions bolt/test/X86/Inputs/dwarf5-df-types-debug-names-main.s
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ main: # @main
.Linfo_string5:
.asciz "f2" # string offset=24
.Linfo_string6:
.asciz "/home/ayermolo/local/tasks/T138552329/typeDedupSplit" # string offset=27
.asciz "." # string offset=27
.Linfo_string7:
.asciz "main.dwo" # string offset=80
.Linfo_string8:
Expand All @@ -234,15 +234,15 @@ main: # @main
.long 19
.long 24
.long 27
.long 80
.long 89
.long 92
.long 97
.long 100
.long 103
.long 106
.long 112
.long 220
.long 29
.long 38
.long 41
.long 46
.long 49
.long 52
.long 55
.long 61
.long 169
.section .debug_info.dwo,"e",@progbits
.long .Ldebug_info_dwo_end2-.Ldebug_info_dwo_start2 # Length of Unit
.Ldebug_info_dwo_start2:
Expand Down Expand Up @@ -474,7 +474,7 @@ main: # @main
.byte 1
.byte 8
.byte 2
.ascii "/home/ayermolo/local/tasks/T138552329/typeDedupSplit"
.ascii "."
.byte 0
.byte 46
.byte 0
Expand Down
198 changes: 198 additions & 0 deletions bolt/test/X86/dwarf5-df-types-modify-dwo-name-mixed.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
; RUN: rm -rf %t
; RUN: mkdir %t
; RUN: cd %t
; RUN: llvm-mc -dwarf-version=5 -filetype=obj -triple x86_64-unknown-linux %p/Inputs/dwarf5-df-types-debug-names-main.s \
; RUN: -split-dwarf-file=main.dwo -o main.o
; RUN: llvm-mc -dwarf-version=5 -filetype=obj -triple x86_64-unknown-linux %p/Inputs/dwarf5-df-types-dup-helper.s \
; RUN: -split-dwarf-file=helper.dwo -o helper.o
; RUN: %clang %cflags -gdwarf-5 -gsplit-dwarf=split main.o helper.o -o main.exe
; RUN: llvm-bolt main.exe -o main.exe.bolt --update-debug-sections
; RUN: llvm-dwarfdump --debug-info -r 0 main.exe.bolt > log.txt
; RUN: llvm-dwarfdump --debug-info -r 0 main.dwo.dwo >> log.txt
; RUN: llvm-dwarfdump --debug-info -r 0 helper.dwo.dwo >> log.txt
; RUN: llvm-dwarfdump --debug-str-offsets main.dwo.dwo >> log.txt
; RUN: llvm-dwarfdump --debug-str-offsets helper.dwo.dwo >> log.txt
; RUN: cat log.txt | FileCheck -check-prefix=BOLT %s

;; Test is a mix of DWARF5 TUs where one has DW_AT_comp_dir/DW_AT_dwo_name, and another one doesn't.
;; Tests that BOLT correctly updates DW_AT_dwo_name for TUs.

; BOLT: DW_TAG_skeleton_unit
; BOLT: DW_AT_comp_dir (".")
; BOLT: DW_AT_dwo_name ("main.dwo.dwo")
; BOLT: DW_TAG_skeleton_unit
; BOLT: DW_AT_comp_dir (".")
; BOLT: DW_AT_dwo_name ("helper.dwo.dwo")
; BOLT: DW_TAG_type_unit
; BOLT: DW_AT_comp_dir (".")
; BOLT: DW_AT_dwo_name ("main.dwo.dwo")
; BOLT: DW_TAG_type_unit
; BOLT: DW_AT_comp_dir (".")
; BOLT: DW_AT_dwo_name ("main.dwo.dwo")
; BOLT: DW_TAG_type_unit
; BOLT-NOT: DW_AT_dwo_name
; BOLT: DW_TAG_type_unit
; BOLT-NOT: DW_AT_dwo_name
; BOLT: DW_TAG_compile_unit
; BOLT: .debug_str_offsets.dwo contents:
; BOLT-NEXT: 0x00000000: Contribution size = 68, Format = DWARF32, Version = 5
; BOLT-NEXT: "main"
; BOLT-NEXT: "int"
; BOLT-NEXT: "argc"
; BOLT-NEXT: "argv"
; BOLT-NEXT: "char"
; BOLT-NEXT: "f2"
; BOLT-NEXT: "."
; BOLT-NEXT: "main.dwo.dwo"
; BOLT-NEXT: "c1"
; BOLT-NEXT: "Foo2"
; BOLT-NEXT: "f3"
; BOLT-NEXT: "c2"
; BOLT-NEXT: "c3"
; BOLT-NEXT: "Foo2a"
; BOLT-NEXT: "clang version 18.0.0git (git@github.com:ayermolo/llvm-project.git db35fa8fc524127079662802c4735dbf397f86d0)"
; BOLT-NEXT: "main.cpp"
; BOLT-NEXT: helper.dwo.dwo: file format elf64-x86-64

; BOLT: .debug_str_offsets.dwo contents:
; BOLT-NEXT: 0x00000000: Contribution size = 64, Format = DWARF32, Version = 5
; BOLT-NEXT: "fooint"
; BOLT-NEXT: "int"
; BOLT-NEXT: "_Z3foov"
; BOLT-NEXT: "foo"
; BOLT-NEXT: "fint"
; BOLT-NEXT: "c1"
; BOLT-NEXT: "c2"
; BOLT-NEXT: "Foo2Int"
; BOLT-NEXT: "f"
; BOLT-NEXT: "char"
; BOLT-NEXT: "c3"
; BOLT-NEXT: "Foo2a"
; BOLT-NEXT: "clang version 18.0.0"
; BOLT-NEXT: "helper.cpp"
; BOLT-NEXT: "helper.dwo"


;; Tests that BOLT correctly handles updating DW_AT_dwo_name when it outputs a DWP file.
;; Currently skipping one of Type units because it is not being de-dupped.
;; In the tu-index this TU is not present.
; RUN: rm main.exe.bolt
; RUN: llvm-bolt main.exe -o main.exe.bolt --update-debug-sections --write-dwp
; RUN: llvm-dwarfdump --debug-info -r 0 main.exe.bolt.dwp > logDWP.txt
; RUN: llvm-dwarfdump --debug-str-offsets main.exe.bolt.dwp >> logDWP.txt
; RUN: cat logDWP.txt | FileCheck -check-prefix=BOLT-DWP %s
; BOLT-DWP: DW_TAG_type_unit
; BOLT-DWP: DW_AT_comp_dir (".")
; BOLT-DWP: DW_AT_dwo_name ("main.dwo.dwo")
; BOLT-DWP: DW_TAG_type_unit
; BOLT-DWP: DW_AT_comp_dir (".")
; BOLT-DWP: DW_AT_dwo_name ("main.dwo.dwo")
; BOLT-DWP: DW_TAG_compile_unit
; BOLT-DWP: DW_AT_dwo_name ("main.dwo.dwo")
; BOLT-DWP: DW_TAG_type_unit
; BOLT-DW-NOT: DW_AT_dwo_name
; BOLT-DWP: Contribution size = 68, Format = DWARF32, Version = 5
; BOLT-DWP-NEXT: "main"
; BOLT-DWP-NEXT: "int"
; BOLT-DWP-NEXT: "argc"
; BOLT-DWP-NEXT: "argv"
; BOLT-DWP-NEXT: "char"
; BOLT-DWP-NEXT: "f2"
; BOLT-DWP-NEXT: "."
; BOLT-DWP-NEXT: "main.dwo.dwo"
; BOLT-DWP-NEXT: "c1"
; BOLT-DWP-NEXT: "Foo2"
; BOLT-DWP-NEXT: "f3"
; BOLT-DWP-NEXT: "c2"
; BOLT-DWP-NEXT: "c3"
; BOLT-DWP-NEXT: "Foo2a"
; BOLT-DWP-NEXT: "clang version 18.0.0git (git@github.com:ayermolo/llvm-project.git db35fa8fc524127079662802c4735dbf397f86d0)"
; BOLT-DWP-NEXT: "main.cpp"
; BOLT-DWP-NEXT: Contribution size = 64, Format = DWARF32, Version = 5
; BOLT-DWP-NEXT: "fooint"
; BOLT-DWP-NEXT: "int"
; BOLT-DWP-NEXT: "_Z3foov"
; BOLT-DWP-NEXT: "foo"
; BOLT-DWP-NEXT: "fint"
; BOLT-DWP-NEXT: "c1"
; BOLT-DWP-NEXT: "c2"
; BOLT-DWP-NEXT: "Foo2Int"
; BOLT-DWP-NEXT: "f"
; BOLT-DWP-NEXT: "char"
; BOLT-DWP-NEXT: "c3"
; BOLT-DWP-NEXT: "Foo2a"
; BOLT-DWP-NEXT: "clang version 18.0.0"
; BOLT-DWP-NEXT: "helper.cpp"
; BOLT-DWP-NEXT: "helper.dwo

;; Tests that BOLT correctly handles updating DW_AT_comp_dir/DW_AT_dwo_name when outptut directory is specified.

; RUN: mkdir DWOOut
; RUN: rm main.exe.bolt
; RUN: llvm-bolt main.exe -o main.exe.bolt --update-debug-sections --dwarf-output-path=%t/DWOOut
; RUN: cd DWOOut
; RUN: llvm-dwarfdump --debug-info -r 0 ../main.exe.bolt > log.txt
; RUN: llvm-dwarfdump --debug-info -r 0 main.dwo0.dwo >> log.txt
; RUN: llvm-dwarfdump --debug-info -r 0 helper.dwo0.dwo >> log.txt
; RUN: llvm-dwarfdump --debug-str-offsets main.dwo0.dwo >> log.txt
; RUN: llvm-dwarfdump --debug-str-offsets helper.dwo0.dwo >> log.txt
; RUN: cat log.txt | FileCheck -check-prefix=BOLT-PATH %s

; BOLT-PATH: DW_TAG_skeleton_unit
; BOLT-PATH: DW_AT_comp_dir ("
; BOLT-PATH-SAME: dwarf5-df-types-modify-dwo-name-mixed.test.tmp/DWOOut
; BOLT-PATH: DW_AT_dwo_name ("main.dwo0.dwo")
; BOLT-PATH: DW_TAG_skeleton_unit
; BOLT-PATH: DW_AT_comp_dir ("
; BOLT-PATH-SAME: dwarf5-df-types-modify-dwo-name-mixed.test.tmp/DWOOut
; BOLT-PATH: DW_AT_dwo_name ("helper.dwo0.dwo")
; BOLT-PATH: DW_TAG_type_unit
; BOLT-PATH: DW_AT_comp_dir ("
; BOLT-PATH-SAME: dwarf5-df-types-modify-dwo-name-mixed.test.tmp/DWOOut
; BOLT-PATH: DW_AT_dwo_name ("main.dwo0.dwo")
; BOLT-PATH: DW_TAG_type_unit
; BOLT-PATH: DW_AT_comp_dir ("
; BOLT-PATH-SAME: dwarf5-df-types-modify-dwo-name-mixed.test.tmp/DWOOut
; BOLT-PATH: DW_AT_dwo_name ("main.dwo0.dwo")
; BOLT-PATH: DW_TAG_type_unit
; BOLT-PATH-NOT: DW_AT_comp_dir
; BOLT-PATH: DW_TAG_type_unit
; BOLT-PATH-NOT: DW_AT_comp_dir
; BOLT-PATH: DW_TAG_compile_unit
; BOLT-PATH: .debug_str_offsets.dwo contents:
; BOLT-PATH-NEXT: 0x00000000: Contribution size = 68, Format = DWARF32, Version = 5
; BOLT-PATH-NEXT: "main"
; BOLT-PATH-NEXT: "int"
; BOLT-PATH-NEXT: "argc"
; BOLT-PATH-NEXT: "argv"
; BOLT-PATH-NEXT: "char"
; BOLT-PATH-NEXT: "f2"
; BOLT-PATH-NEXT: dwarf5-df-types-modify-dwo-name-mixed.test.tmp/DWOOut"
; BOLT-PATH-NEXT: "main.dwo0.dwo"
; BOLT-PATH-NEXT: "c1"
; BOLT-PATH-NEXT: "Foo2"
; BOLT-PATH-NEXT: "f3"
; BOLT-PATH-NEXT: "c2"
; BOLT-PATH-NEXT: "c3"
; BOLT-PATH-NEXT: "Foo2a"
; BOLT-PATH-NEXT: "clang version 18.0.0git (git@github.com:ayermolo/llvm-project.git db35fa8fc524127079662802c4735dbf397f86d0)"
; BOLT-PATH-NEXT: "main.cpp"
; BOLT-PATH-NEXT: helper.dwo0.dwo: file format elf64-x86-64

; BOLT-PATH: .debug_str_offsets.dwo contents:
; BOLT-PATH-NEXT: Contribution size = 64, Format = DWARF32, Version = 5
; BOLT-PATH-NEXT: "fooint"
; BOLT-PATH-NEXT: "int"
; BOLT-PATH-NEXT: "_Z3foov"
; BOLT-PATH-NEXT: "foo"
; BOLT-PATH-NEXT: "fint"
; BOLT-PATH-NEXT: "c1"
; BOLT-PATH-NEXT: "c2"
; BOLT-PATH-NEXT: "Foo2Int"
; BOLT-PATH-NEXT: "f"
; BOLT-PATH-NEXT: "char"
; BOLT-PATH-NEXT: "c3"
; BOLT-PATH-NEXT: "Foo2a"
; BOLT-PATH-NEXT: "clang version 18.0.0"
; BOLT-PATH-NEXT: "helper.cpp"
; BOLT-PATH-NEXT: "helper.dwo"
175 changes: 175 additions & 0 deletions bolt/test/X86/dwarf5-df-types-modify-dwo-name.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
; RUN: rm -rf %t
; RUN: mkdir %t
; RUN: cd %t
; RUN: llvm-mc -dwarf-version=5 -filetype=obj -triple x86_64-unknown-linux %p/Inputs/dwarf5-df-types-debug-names-main.s \
; RUN: -split-dwarf-file=main.dwo -o main.o
; RUN: llvm-mc -dwarf-version=5 -filetype=obj -triple x86_64-unknown-linux %p/Inputs/dwarf5-df-types-debug-names-helper.s \
; RUN: -split-dwarf-file=helper.dwo -o helper.o
; RUN: %clang %cflags -gdwarf-5 -gsplit-dwarf=split main.o helper.o -o main.exe
; RUN: llvm-bolt main.exe -o main.exe.bolt --update-debug-sections
; RUN: llvm-dwarfdump --debug-info -r 0 main.exe.bolt > log.txt
; RUN: llvm-dwarfdump --debug-info -r 0 main.dwo.dwo >> log.txt
; RUN: llvm-dwarfdump --debug-info -r 0 helper.dwo.dwo >> log.txt
; RUN: llvm-dwarfdump --debug-str-offsets main.dwo.dwo >> log.txt
; RUN: llvm-dwarfdump --debug-str-offsets helper.dwo.dwo >> log.txt
; RUN: cat log.txt | FileCheck -check-prefix=BOLT %s

;; Tests that BOLT correctly updates DW_AT_dwo_name for TU Untis.

; BOLT: DW_TAG_skeleton_unit
; BOLT: DW_AT_comp_dir (".")
; BOLT: DW_AT_dwo_name ("main.dwo.dwo")
; BOLT: DW_TAG_skeleton_unit
; BOLT: DW_AT_comp_dir (".")
; BOLT: DW_AT_dwo_name ("helper.dwo.dwo")
; BOLT: DW_TAG_type_unit
; BOLT: DW_AT_comp_dir (".")
; BOLT: DW_AT_dwo_name ("main.dwo.dwo")
; BOLT: DW_TAG_type_unit
; BOLT: DW_AT_comp_dir (".")
; BOLT: DW_AT_dwo_name ("main.dwo.dwo")
; BOLT: DW_TAG_type_unit
; BOLT: DW_AT_comp_dir (".")
; BOLT: DW_AT_dwo_name ("helper.dwo.dwo")
; BOLT: DW_TAG_type_unit
; BOLT: DW_AT_comp_dir (".")
; BOLT: DW_AT_dwo_name ("helper.dwo.dwo")
; BOLT: .debug_str_offsets.dwo contents:
; BOLT-NEXT: 0x00000000: Contribution size = 68, Format = DWARF32, Version = 5
; BOLT-NEXT: "main"
; BOLT-NEXT: "int"
; BOLT-NEXT: "argc"
; BOLT-NEXT: "argv"
; BOLT-NEXT: "char"
; BOLT-NEXT: "f2"
; BOLT-NEXT: "."
; BOLT-NEXT: "main.dwo.dwo"
; BOLT-NEXT: "c1"
; BOLT-NEXT: "Foo2"
; BOLT-NEXT: "f3"
; BOLT-NEXT: "c2"
; BOLT-NEXT: "c3"
; BOLT-NEXT: "Foo2a"
; BOLT-NEXT: "clang version 18.0.0git (git@github.com:ayermolo/llvm-project.git db35fa8fc524127079662802c4735dbf397f86d0)"
; BOLT-NEXT: "main.cpp"
; BOLT-NEXT: helper.dwo.dwo: file format elf64-x86-64

; BOLT: .debug_str_offsets.dwo contents:
; BOLT-NEXT: 0x00000000: Contribution size = 68, Format = DWARF32, Version = 5
; BOLT-NEXT: "fooint"
; BOLT-NEXT: "int"
; BOLT-NEXT: "_Z3foov"
; BOLT-NEXT: "foo"
; BOLT-NEXT: "fint"
; BOLT-NEXT: "."
; BOLT-NEXT: "helper.dwo.dwo"
; BOLT-NEXT: "c1"
; BOLT-NEXT: "c2"
; BOLT-NEXT: "Foo2Int"
; BOLT-NEXT: "f"
; BOLT-NEXT: "char"
; BOLT-NEXT: "c3"
; BOLT-NEXT: "Foo2a"
; BOLT-NEXT: "clang version 18.0.0git (git@github.com:ayermolo/llvm-project.git db35fa8fc524127079662802c4735dbf397f86d0)"
; BOLT-NEXT: "helper.cpp"


;; Tests that BOLT correctly handles updating DW_AT_dwo_name when it outputs a DWP file.
;; Currently skipping one of Type units because it is not being de-dupped.
;; In the tu-index this TU is not present.
; RUN: rm main.exe.bolt
; RUN: llvm-bolt main.exe -o main.exe.bolt --update-debug-sections --write-dwp
; RUN: llvm-dwarfdump --debug-info -r 0 main.exe.bolt.dwp > logDWP.txt
; RUN: llvm-dwarfdump --debug-str-offsets main.exe.bolt.dwp >> logDWP.txt
; RUN: cat logDWP.txt | FileCheck -check-prefix=BOLT-DWP %s
; BOLT-DWP: DW_TAG_type_unit
; BOLT-DWP: DW_AT_comp_dir (".")
; BOLT-DWP: DW_AT_dwo_name ("main.dwo.dwo")
; BOLT-DWP: DW_TAG_type_unit
; BOLT-DWP: DW_AT_comp_dir (".")
; BOLT-DWP: DW_AT_dwo_name ("main.dwo.dwo")
; BOLT-DWP: DW_TAG_compile_unit
; BOLT-DWP: DW_AT_dwo_name ("main.dwo.dwo")
; BOLT-DWP: DW_TAG_type_unit
; BOLT-DWP: DW_AT_comp_dir (".")
; BOLT-DWP: DW_AT_dwo_name ("helper.dwo.dwo")
; BOLT-DWP: DW_TAG_type_unit
; BOLT-DWP: DW_TAG_compile_unit
; BOLT-DWP: DW_AT_name ("helper.cpp")
; BOLT-DWP: DW_AT_dwo_name ("helper.dwo.dwo")

;; Tests that BOLT correctly handles updating DW_AT_comp_dir/DW_AT_dwo_name when outptut directory is specified.

; RUN: mkdir DWOOut
; RUN: rm main.exe.bolt
; RUN: llvm-bolt main.exe -o main.exe.bolt --update-debug-sections --dwarf-output-path=%t/DWOOut
; RUN: cd DWOOut
; RUN: llvm-dwarfdump --debug-info -r 0 ../main.exe.bolt > log.txt
; RUN: llvm-dwarfdump --debug-info -r 0 main.dwo0.dwo >> log.txt
; RUN: llvm-dwarfdump --debug-info -r 0 helper.dwo0.dwo >> log.txt
; RUN: llvm-dwarfdump --debug-str-offsets main.dwo0.dwo >> log.txt
; RUN: llvm-dwarfdump --debug-str-offsets helper.dwo0.dwo >> log.txt
; RUN: cat log.txt | FileCheck -check-prefix=BOLT-PATH %s

; BOLT-PATH: DW_TAG_skeleton_unit
; BOLT-PATH: DW_AT_comp_dir ("
; BOLT-PATH-SAME: dwarf5-df-types-modify-dwo-name.test.tmp/DWOOut
; BOLT-PATH: DW_AT_dwo_name ("main.dwo0.dwo")
; BOLT-PATH: DW_TAG_skeleton_unit
; BOLT-PATH: DW_AT_comp_dir ("
; BOLT-PATH-SAME: dwarf5-df-types-modify-dwo-name.test.tmp/DWOOut
; BOLT-PATH: DW_AT_dwo_name ("helper.dwo0.dwo")
; BOLT-PATH: DW_TAG_type_unit
; BOLT-PATH: DW_AT_comp_dir ("
; BOLT-PATH-SAME: dwarf5-df-types-modify-dwo-name.test.tmp/DWOOut
; BOLT-PATH: DW_AT_dwo_name ("main.dwo0.dwo")
; BOLT-PATH: DW_TAG_type_unit
; BOLT-PATH: DW_AT_comp_dir ("
; BOLT-PATH-SAME: dwarf5-df-types-modify-dwo-name.test.tmp/DWOOut
; BOLT-PATH: DW_AT_dwo_name ("main.dwo0.dwo")
; BOLT-PATH: DW_TAG_type_unit
; BOLT-PATH: DW_AT_comp_dir ("
; BOLT-PATH-SAME: dwarf5-df-types-modify-dwo-name.test.tmp/DWOOut
; BOLT-PATH: DW_AT_dwo_name ("helper.dwo0.dwo")
; BOLT-PATH: DW_TAG_type_unit
; BOLT-PATH: DW_AT_comp_dir ("
; BOLT-PATH-SAME: dwarf5-df-types-modify-dwo-name.test.tmp/DWOOut
; BOLT-PATH: DW_AT_dwo_name ("helper.dwo0.dwo")
; BOLT-PATH: .debug_str_offsets.dwo contents:
; BOLT-PATH-NEXT: 0x00000000: Contribution size = 68, Format = DWARF32, Version = 5
; BOLT-PATH-NEXT: "main"
; BOLT-PATH-NEXT: "int"
; BOLT-PATH-NEXT: "argc"
; BOLT-PATH-NEXT: "argv"
; BOLT-PATH-NEXT: "char"
; BOLT-PATH-NEXT: "f2"
; BOLT-PATH-NEXT: dwarf5-df-types-modify-dwo-name.test.tmp/DWOOut"
; BOLT-PATH-NEXT: "main.dwo0.dwo"
; BOLT-PATH-NEXT: "c1"
; BOLT-PATH-NEXT: "Foo2"
; BOLT-PATH-NEXT: "f3"
; BOLT-PATH-NEXT: "c2"
; BOLT-PATH-NEXT: "c3"
; BOLT-PATH-NEXT: "Foo2a"
; BOLT-PATH-NEXT: "clang version 18.0.0git (git@github.com:ayermolo/llvm-project.git db35fa8fc524127079662802c4735dbf397f86d0)"
; BOLT-PATH-NEXT: "main.cpp"
; BOLT-PATH-NEXT: helper.dwo0.dwo: file format elf64-x86-64

; BOLT-PATH: .debug_str_offsets.dwo contents:
; BOLT-PATH-NEXT: 0x00000000: Contribution size = 68, Format = DWARF32, Version = 5
; BOLT-PATH-NEXT: "fooint"
; BOLT-PATH-NEXT: "int"
; BOLT-PATH-NEXT: "_Z3foov"
; BOLT-PATH-NEXT: "foo"
; BOLT-PATH-NEXT: "fint"
; BOLT-PATH-NEXT: dwarf5-df-types-modify-dwo-name.test.tmp/DWOOut"
; BOLT-PATH-NEXT: "helper.dwo0.dwo"
; BOLT-PATH-NEXT: "c1"
; BOLT-PATH-NEXT: "c2"
; BOLT-PATH-NEXT: "Foo2Int"
; BOLT-PATH-NEXT: "f"
; BOLT-PATH-NEXT: "char"
; BOLT-PATH-NEXT: "c3"
; BOLT-PATH-NEXT: "Foo2a"
; BOLT-PATH-NEXT: "clang version 18.0.0git (git@github.com:ayermolo/llvm-project.git db35fa8fc524127079662802c4735dbf397f86d0)"
; BOLT-PATH-NEXT: "helper.cpp"
9 changes: 5 additions & 4 deletions bolt/test/X86/profile-passthrough-block.test
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
## This reproduces a bug with BOLT setting incorrect discriminator for
## secondary entry points in YAML profile.
## Test YAMLProfileReader support for pass-through blocks in non-matching edges:
## match the profile edge A -> C to the CFG with blocks A -> B -> C.

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

# CHECK: BOLT-INFO: 1 out of 1 functions in the binary (100.0%) have non-empty execution profile
# CHECK: Binary Function "main" after building cfg
# CHECK: Profile Acc : 100.0%
# CHECK-NOT: BOLT-WARNING: no successor for block .LFT0 that matches index 3 or block .Ltmp0

#--- main.s
Expand Down
22 changes: 22 additions & 0 deletions clang-tools-extra/clang-query/Query.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//

#include "Query.h"
#include "QueryParser.h"
#include "QuerySession.h"
#include "clang/AST/ASTDumper.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
Expand Down Expand Up @@ -281,5 +282,26 @@ const QueryKind SetQueryKind<bool>::value;
const QueryKind SetQueryKind<OutputKind>::value;
#endif

bool FileQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {
auto Buffer = llvm::MemoryBuffer::getFile(StringRef{File}.trim());
if (!Buffer) {
if (Prefix.has_value())
llvm::errs() << *Prefix << ": ";
llvm::errs() << "cannot open " << File << ": "
<< Buffer.getError().message() << "\n";
return false;
}

StringRef FileContentRef(Buffer.get()->getBuffer());

while (!FileContentRef.empty()) {
QueryRef Q = QueryParser::parse(FileContentRef, QS);
if (!Q->run(llvm::outs(), QS))
return false;
FileContentRef = Q->RemainingContent;
}
return true;
}

} // namespace query
} // namespace clang
18 changes: 17 additions & 1 deletion clang-tools-extra/clang-query/Query.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ enum QueryKind {
QK_SetTraversalKind,
QK_EnableOutputKind,
QK_DisableOutputKind,
QK_Quit
QK_Quit,
QK_File
};

class QuerySession;
Expand Down Expand Up @@ -188,6 +189,21 @@ struct DisableOutputQuery : SetNonExclusiveOutputQuery {
}
};

struct FileQuery : Query {
FileQuery(StringRef File, StringRef Prefix = StringRef())
: Query(QK_File), File(File),
Prefix(!Prefix.empty() ? std::optional<std::string>(Prefix)
: std::nullopt) {}

bool run(llvm::raw_ostream &OS, QuerySession &QS) const override;

static bool classof(const Query *Q) { return Q->Kind == QK_File; }

private:
std::string File;
std::optional<std::string> Prefix;
};

} // namespace query
} // namespace clang

Expand Down
10 changes: 8 additions & 2 deletions clang-tools-extra/clang-query/QueryParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,8 @@ enum ParsedQueryKind {
PQK_Unlet,
PQK_Quit,
PQK_Enable,
PQK_Disable
PQK_Disable,
PQK_File
};

enum ParsedQueryVariable {
Expand Down Expand Up @@ -222,12 +223,14 @@ QueryRef QueryParser::doParse() {
.Case("let", PQK_Let)
.Case("m", PQK_Match, /*IsCompletion=*/false)
.Case("match", PQK_Match)
.Case("q", PQK_Quit, /*IsCompletion=*/false)
.Case("q", PQK_Quit, /*IsCompletion=*/false)
.Case("quit", PQK_Quit)
.Case("set", PQK_Set)
.Case("enable", PQK_Enable)
.Case("disable", PQK_Disable)
.Case("unlet", PQK_Unlet)
.Case("f", PQK_File, /*IsCompletion=*/false)
.Case("file", PQK_File)
.Default(PQK_Invalid);

switch (QKind) {
Expand Down Expand Up @@ -351,6 +354,9 @@ QueryRef QueryParser::doParse() {
return endQuery(new LetQuery(Name, VariantValue()));
}

case PQK_File:
return new FileQuery(Line);

case PQK_Invalid:
return new InvalidQuery("unknown command: " + CommandStr);
}
Expand Down
18 changes: 2 additions & 16 deletions clang-tools-extra/clang-query/tool/ClangQuery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,22 +74,8 @@ static cl::opt<std::string> PreloadFile(

bool runCommandsInFile(const char *ExeName, std::string const &FileName,
QuerySession &QS) {
auto Buffer = llvm::MemoryBuffer::getFile(FileName);
if (!Buffer) {
llvm::errs() << ExeName << ": cannot open " << FileName << ": "
<< Buffer.getError().message() << "\n";
return true;
}

StringRef FileContentRef(Buffer.get()->getBuffer());

while (!FileContentRef.empty()) {
QueryRef Q = QueryParser::parse(FileContentRef, QS);
if (!Q->run(llvm::outs(), QS))
return true;
FileContentRef = Q->RemainingContent;
}
return false;
FileQuery Query(FileName, ExeName);
return !Query.run(llvm::errs(), QS);
}

int main(int argc, const char **argv) {
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-tidy/ClangTidyCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ std::optional<int64_t> ClangTidyCheck::OptionsView::getEnumInt(
if (IgnoreCase) {
if (Value.equals_insensitive(NameAndEnum.second))
return NameAndEnum.first;
} else if (Value.equals(NameAndEnum.second)) {
} else if (Value == NameAndEnum.second) {
return NameAndEnum.first;
} else if (Value.equals_insensitive(NameAndEnum.second)) {
Closest = NameAndEnum.second;
Expand Down
28 changes: 17 additions & 11 deletions clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,18 @@ ClangTidyDiagnosticConsumer::ClangTidyDiagnosticConsumer(
: Context(Ctx), ExternalDiagEngine(ExternalDiagEngine),
RemoveIncompatibleErrors(RemoveIncompatibleErrors),
GetFixesFromNotes(GetFixesFromNotes),
EnableNolintBlocks(EnableNolintBlocks) {}
EnableNolintBlocks(EnableNolintBlocks) {

if (Context.getOptions().HeaderFilterRegex &&
!Context.getOptions().HeaderFilterRegex->empty())
HeaderFilter =
std::make_unique<llvm::Regex>(*Context.getOptions().HeaderFilterRegex);

if (Context.getOptions().ExcludeHeaderFilterRegex &&
!Context.getOptions().ExcludeHeaderFilterRegex->empty())
ExcludeHeaderFilter = std::make_unique<llvm::Regex>(
*Context.getOptions().ExcludeHeaderFilterRegex);
}

void ClangTidyDiagnosticConsumer::finalizeLastError() {
if (!Errors.empty()) {
Expand Down Expand Up @@ -562,22 +573,17 @@ void ClangTidyDiagnosticConsumer::checkFilters(SourceLocation Location,
}

StringRef FileName(File->getName());
LastErrorRelatesToUserCode = LastErrorRelatesToUserCode ||
Sources.isInMainFile(Location) ||
getHeaderFilter()->match(FileName);
LastErrorRelatesToUserCode =
LastErrorRelatesToUserCode || Sources.isInMainFile(Location) ||
(HeaderFilter &&
(HeaderFilter->match(FileName) &&
!(ExcludeHeaderFilter && ExcludeHeaderFilter->match(FileName))));

unsigned LineNumber = Sources.getExpansionLineNumber(Location);
LastErrorPassesLineFilter =
LastErrorPassesLineFilter || passesLineFilter(FileName, LineNumber);
}

llvm::Regex *ClangTidyDiagnosticConsumer::getHeaderFilter() {
if (!HeaderFilter)
HeaderFilter =
std::make_unique<llvm::Regex>(*Context.getOptions().HeaderFilterRegex);
return HeaderFilter.get();
}

void ClangTidyDiagnosticConsumer::removeIncompatibleErrors() {
// Each error is modelled as the set of intervals in which it applies
// replacements. To detect overlapping replacements, we use a sweep line
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.h
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ class ClangTidyDiagnosticConsumer : public DiagnosticConsumer {
bool EnableNolintBlocks;
std::vector<ClangTidyError> Errors;
std::unique_ptr<llvm::Regex> HeaderFilter;
std::unique_ptr<llvm::Regex> ExcludeHeaderFilter;
bool LastErrorRelatesToUserCode = false;
bool LastErrorPassesLineFilter = false;
bool LastErrorWasIgnored = false;
Expand Down
6 changes: 5 additions & 1 deletion clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ template <> struct MappingTraits<ClangTidyOptions> {
IO.mapOptional("ImplementationFileExtensions",
Options.ImplementationFileExtensions);
IO.mapOptional("HeaderFilterRegex", Options.HeaderFilterRegex);
IO.mapOptional("ExcludeHeaderFilterRegex",
Options.ExcludeHeaderFilterRegex);
IO.mapOptional("FormatStyle", Options.FormatStyle);
IO.mapOptional("User", Options.User);
IO.mapOptional("CheckOptions", Options.CheckOptions);
Expand All @@ -191,7 +193,8 @@ ClangTidyOptions ClangTidyOptions::getDefaults() {
Options.WarningsAsErrors = "";
Options.HeaderFileExtensions = {"", "h", "hh", "hpp", "hxx"};
Options.ImplementationFileExtensions = {"c", "cc", "cpp", "cxx"};
Options.HeaderFilterRegex = "";
Options.HeaderFilterRegex = std::nullopt;
Options.ExcludeHeaderFilterRegex = std::nullopt;
Options.SystemHeaders = false;
Options.FormatStyle = "none";
Options.User = std::nullopt;
Expand Down Expand Up @@ -231,6 +234,7 @@ ClangTidyOptions &ClangTidyOptions::mergeWith(const ClangTidyOptions &Other,
overrideValue(ImplementationFileExtensions,
Other.ImplementationFileExtensions);
overrideValue(HeaderFilterRegex, Other.HeaderFilterRegex);
overrideValue(ExcludeHeaderFilterRegex, Other.ExcludeHeaderFilterRegex);
overrideValue(SystemHeaders, Other.SystemHeaders);
overrideValue(FormatStyle, Other.FormatStyle);
overrideValue(User, Other.User);
Expand Down
4 changes: 4 additions & 0 deletions clang-tools-extra/clang-tidy/ClangTidyOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ struct ClangTidyOptions {
/// main files will always be displayed.
std::optional<std::string> HeaderFilterRegex;

/// \brief Exclude warnings from headers matching this filter, even if they
/// match \c HeaderFilterRegex.
std::optional<std::string> ExcludeHeaderFilterRegex;

/// Output warnings from system headers matching \c HeaderFilterRegex.
std::optional<bool> SystemHeaders;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ AST_MATCHER(QualType, isEnableIf) {
const NamedDecl *TypeDecl =
Spec->getTemplateName().getAsTemplateDecl()->getTemplatedDecl();
return TypeDecl->isInStdNamespace() &&
(TypeDecl->getName().equals("enable_if") ||
TypeDecl->getName().equals("enable_if_t"));
(TypeDecl->getName() == "enable_if" ||
TypeDecl->getName() == "enable_if_t");
};
const Type *BaseType = Node.getTypePtr();
// Case: pointer or reference to enable_if.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,20 @@
#include "ImplicitWideningOfMultiplicationResultCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchersMacros.h"
#include "clang/Lex/Lexer.h"
#include <optional>

using namespace clang::ast_matchers;

namespace clang {
namespace clang::tidy::bugprone {

namespace {
AST_MATCHER(ImplicitCastExpr, isPartOfExplicitCast) {
return Node.isPartOfExplicitCast();
}
AST_MATCHER(Expr, containsErrors) { return Node.containsErrors(); }
} // namespace
} // namespace clang

namespace clang::tidy::bugprone {

static const Expr *getLHSOfMulBinOp(const Expr *E) {
assert(E == E->IgnoreParens() && "Already skipped all parens!");
Expand Down Expand Up @@ -250,7 +250,8 @@ void ImplicitWideningOfMultiplicationResultCheck::handlePointerOffsetting(

void ImplicitWideningOfMultiplicationResultCheck::registerMatchers(
MatchFinder *Finder) {
Finder->addMatcher(implicitCastExpr(unless(anyOf(isInTemplateInstantiation(),
Finder->addMatcher(implicitCastExpr(unless(anyOf(containsErrors(),
isInTemplateInstantiation(),
isPartOfExplicitCast())),
hasCastKind(CK_IntegralCast))
.bind("x"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ void OptionalValueConversionCheck::registerMatchers(MatchFinder *Finder) {
ofClass(matchers::matchesAnyListedName(OptionalTypes)))),
hasType(ConstructTypeMatcher),
hasArgument(0U, ignoringImpCasts(anyOf(OptionalDereferenceMatcher,
StdMoveCallMatcher))))
StdMoveCallMatcher))),
unless(anyOf(hasAncestor(typeLoc()),
hasAncestor(expr(matchers::hasUnevaluatedContext())))))
.bind("expr"),
this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,7 @@ void SuspiciousEnumUsageCheck::check(const MatchFinder::MatchResult &Result) {
// Skip when one of the parameters is an empty enum. The
// hasDisjointValueRange function could not decide the values properly in
// case of an empty enum.
if (EnumDec->enumerator_begin() == EnumDec->enumerator_end() ||
OtherEnumDec->enumerator_begin() == OtherEnumDec->enumerator_end())
if (EnumDec->enumerators().empty() || OtherEnumDec->enumerators().empty())
return;

if (!hasDisjointValueRange(EnumDec, OtherEnumDec))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,7 @@ static std::string createReplacementText(const LambdaExpr *Lambda) {
AppendName("this");
}
}
if (!Replacement.empty() &&
Lambda->explicit_capture_begin() != Lambda->explicit_capture_end()) {
if (!Replacement.empty() && !Lambda->explicit_captures().empty()) {
// Add back separator if we are adding explicit capture variables.
Stream << ", ";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,44 @@ SpecialMemberFunctionsCheck::SpecialMemberFunctionsCheck(
"AllowMissingMoveFunctions", false)),
AllowSoleDefaultDtor(Options.get("AllowSoleDefaultDtor", false)),
AllowMissingMoveFunctionsWhenCopyIsDeleted(
Options.get("AllowMissingMoveFunctionsWhenCopyIsDeleted", false)) {}
Options.get("AllowMissingMoveFunctionsWhenCopyIsDeleted", false)),
AllowImplicitlyDeletedCopyOrMove(
Options.get("AllowImplicitlyDeletedCopyOrMove", false)) {}

void SpecialMemberFunctionsCheck::storeOptions(
ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "AllowMissingMoveFunctions", AllowMissingMoveFunctions);
Options.store(Opts, "AllowSoleDefaultDtor", AllowSoleDefaultDtor);
Options.store(Opts, "AllowMissingMoveFunctionsWhenCopyIsDeleted",
AllowMissingMoveFunctionsWhenCopyIsDeleted);
Options.store(Opts, "AllowImplicitlyDeletedCopyOrMove",
AllowImplicitlyDeletedCopyOrMove);
}

std::optional<TraversalKind>
SpecialMemberFunctionsCheck::getCheckTraversalKind() const {
return AllowImplicitlyDeletedCopyOrMove ? TK_AsIs
: TK_IgnoreUnlessSpelledInSource;
}

void SpecialMemberFunctionsCheck::registerMatchers(MatchFinder *Finder) {
auto IsNotImplicitOrDeleted = anyOf(unless(isImplicit()), isDeleted());

Finder->addMatcher(
cxxRecordDecl(
eachOf(has(cxxDestructorDecl().bind("dtor")),
has(cxxConstructorDecl(isCopyConstructor()).bind("copy-ctor")),
has(cxxMethodDecl(isCopyAssignmentOperator())
unless(isImplicit()),
eachOf(has(cxxDestructorDecl(unless(isImplicit())).bind("dtor")),
has(cxxConstructorDecl(isCopyConstructor(),
IsNotImplicitOrDeleted)
.bind("copy-ctor")),
has(cxxMethodDecl(isCopyAssignmentOperator(),
IsNotImplicitOrDeleted)
.bind("copy-assign")),
has(cxxConstructorDecl(isMoveConstructor()).bind("move-ctor")),
has(cxxMethodDecl(isMoveAssignmentOperator())
has(cxxConstructorDecl(isMoveConstructor(),
IsNotImplicitOrDeleted)
.bind("move-ctor")),
has(cxxMethodDecl(isMoveAssignmentOperator(),
IsNotImplicitOrDeleted)
.bind("move-assign"))))
.bind("class-def"),
this);
Expand Down Expand Up @@ -127,7 +146,8 @@ void SpecialMemberFunctionsCheck::check(
for (const auto &KV : Matchers)
if (const auto *MethodDecl =
Result.Nodes.getNodeAs<CXXMethodDecl>(KV.first)) {
StoreMember({KV.second, MethodDecl->isDeleted()});
StoreMember(
{KV.second, MethodDecl->isDeleted(), MethodDecl->isImplicit()});
}
}

Expand All @@ -144,7 +164,13 @@ void SpecialMemberFunctionsCheck::checkForMissingMembers(

auto HasMember = [&](SpecialMemberFunctionKind Kind) {
return llvm::any_of(DefinedMembers, [Kind](const auto &Data) {
return Data.FunctionKind == Kind;
return Data.FunctionKind == Kind && !Data.IsImplicit;
});
};

auto HasImplicitDeletedMember = [&](SpecialMemberFunctionKind Kind) {
return llvm::any_of(DefinedMembers, [Kind](const auto &Data) {
return Data.FunctionKind == Kind && Data.IsImplicit && Data.IsDeleted;
});
};

Expand All @@ -154,9 +180,17 @@ void SpecialMemberFunctionsCheck::checkForMissingMembers(
});
};

auto RequireMember = [&](SpecialMemberFunctionKind Kind) {
if (!HasMember(Kind))
MissingMembers.push_back(Kind);
auto RequireMembers = [&](SpecialMemberFunctionKind Kind1,
SpecialMemberFunctionKind Kind2) {
if (AllowImplicitlyDeletedCopyOrMove && HasImplicitDeletedMember(Kind1) &&
HasImplicitDeletedMember(Kind2))
return;

if (!HasMember(Kind1))
MissingMembers.push_back(Kind1);

if (!HasMember(Kind2))
MissingMembers.push_back(Kind2);
};

bool RequireThree =
Expand All @@ -180,23 +214,25 @@ void SpecialMemberFunctionsCheck::checkForMissingMembers(
!HasMember(SpecialMemberFunctionKind::NonDefaultDestructor))
MissingMembers.push_back(SpecialMemberFunctionKind::Destructor);

RequireMember(SpecialMemberFunctionKind::CopyConstructor);
RequireMember(SpecialMemberFunctionKind::CopyAssignment);
RequireMembers(SpecialMemberFunctionKind::CopyConstructor,
SpecialMemberFunctionKind::CopyAssignment);
}

if (RequireFive &&
!(AllowMissingMoveFunctionsWhenCopyIsDeleted &&
(IsDeleted(SpecialMemberFunctionKind::CopyConstructor) &&
IsDeleted(SpecialMemberFunctionKind::CopyAssignment)))) {
assert(RequireThree);
RequireMember(SpecialMemberFunctionKind::MoveConstructor);
RequireMember(SpecialMemberFunctionKind::MoveAssignment);
RequireMembers(SpecialMemberFunctionKind::MoveConstructor,
SpecialMemberFunctionKind::MoveAssignment);
}

if (!MissingMembers.empty()) {
llvm::SmallVector<SpecialMemberFunctionKind, 5> DefinedMemberKinds;
llvm::transform(DefinedMembers, std::back_inserter(DefinedMemberKinds),
[](const auto &Data) { return Data.FunctionKind; });
for (const auto &Data : DefinedMembers) {
if (!Data.IsImplicit)
DefinedMemberKinds.push_back(Data.FunctionKind);
}
diag(ID.first, "class '%0' defines %1 but does not define %2")
<< ID.second << cppcoreguidelines::join(DefinedMemberKinds, " and ")
<< cppcoreguidelines::join(MissingMembers, " or ");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,8 @@ class SpecialMemberFunctionsCheck : public ClangTidyCheck {
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
void onEndOfTranslationUnit() override;
std::optional<TraversalKind> getCheckTraversalKind() const override {
return TK_IgnoreUnlessSpelledInSource;
}
std::optional<TraversalKind> getCheckTraversalKind() const override;

enum class SpecialMemberFunctionKind : uint8_t {
Destructor,
DefaultDestructor,
Expand All @@ -46,6 +45,7 @@ class SpecialMemberFunctionsCheck : public ClangTidyCheck {
struct SpecialMemberFunctionData {
SpecialMemberFunctionKind FunctionKind;
bool IsDeleted;
bool IsImplicit = false;

bool operator==(const SpecialMemberFunctionData &Other) const {
return (Other.FunctionKind == FunctionKind) &&
Expand All @@ -67,6 +67,7 @@ class SpecialMemberFunctionsCheck : public ClangTidyCheck {
const bool AllowMissingMoveFunctions;
const bool AllowSoleDefaultDtor;
const bool AllowMissingMoveFunctionsWhenCopyIsDeleted;
const bool AllowImplicitlyDeletedCopyOrMove;
ClassDefiningSpecialMembersMap ClassWithSpecialMembers;
};

Expand Down
4 changes: 1 addition & 3 deletions clang-tools-extra/clang-tidy/misc/UnusedParametersCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,7 @@ void UnusedParametersCheck::check(const MatchFinder::MatchResult &Result) {

// In non-strict mode ignore function definitions with empty bodies
// (constructor initializer counts for non-empty body).
if (StrictMode ||
(Function->getBody()->child_begin() !=
Function->getBody()->child_end()) ||
if (StrictMode || !Function->getBody()->children().empty() ||
(isa<CXXConstructorDecl>(Function) &&
cast<CXXConstructorDecl>(Function)->getNumCtorInitializers() > 0))
warnOnUnusedParameter(Result, Function, I);
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ add_clang_library(clangTidyModernizeModule
UseNullptrCheck.cpp
UseOverrideCheck.cpp
UseStartsEndsWithCheck.cpp
UseStdFormatCheck.cpp
UseStdNumbersCheck.cpp
UseStdPrintCheck.cpp
UseTrailingReturnTypeCheck.cpp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ getContainerFromBeginEndCall(const Expr *Init, bool IsBegin, bool *IsArrow,
return {};
if (IsReverse && !Call->Name.consume_back("r"))
return {};
if (!Call->Name.empty() && !Call->Name.equals("c"))
if (!Call->Name.empty() && Call->Name != "c")
return {};
return std::make_pair(Call->Container, Call->CallKind);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,17 +129,17 @@ generateReplacements(const MatchFinder::MatchResult &Match,
continue;
}

// if the nested call is not the same as the top call
if (InnerCall->getDirectCallee()->getQualifiedNameAsString() !=
TopCall->getDirectCallee()->getQualifiedNameAsString())
continue;

const FindArgsResult InnerResult = findArgs(InnerCall);

// if the nested call doesn't have arguments skip it
if (!InnerResult.First || !InnerResult.Last)
continue;

// if the nested call is not the same as the top call
if (InnerCall->getDirectCallee()->getQualifiedNameAsString() !=
TopCall->getDirectCallee()->getQualifiedNameAsString())
continue;

// if the nested call doesn't have the same compare function
if ((Result.Compare || InnerResult.Compare) &&
!utils::areStatementsIdentical(Result.Compare, InnerResult.Compare,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include "UseNullptrCheck.h"
#include "UseOverrideCheck.h"
#include "UseStartsEndsWithCheck.h"
#include "UseStdFormatCheck.h"
#include "UseStdNumbersCheck.h"
#include "UseStdPrintCheck.h"
#include "UseTrailingReturnTypeCheck.h"
Expand Down Expand Up @@ -76,6 +77,7 @@ class ModernizeModule : public ClangTidyModule {
"modernize-use-designated-initializers");
CheckFactories.registerCheck<UseStartsEndsWithCheck>(
"modernize-use-starts-ends-with");
CheckFactories.registerCheck<UseStdFormatCheck>("modernize-use-std-format");
CheckFactories.registerCheck<UseStdNumbersCheck>(
"modernize-use-std-numbers");
CheckFactories.registerCheck<UseStdPrintCheck>("modernize-use-std-print");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ AST_MATCHER(FunctionDecl, hasOtherDeclarations) {
void UseConstraintsCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(
functionTemplateDecl(
// Skip external libraries included as system headers
unless(isExpansionInSystemHeader()),
has(functionDecl(unless(hasOtherDeclarations()), isDefinition(),
hasReturnTypeLoc(typeLoc().bind("return")))
.bind("function")))
Expand All @@ -57,6 +59,8 @@ matchEnableIfSpecializationImplTypename(TypeLoc TheType) {
return std::nullopt;
}
TheType = Dep.getQualifierLoc().getTypeLoc();
if (TheType.isNull())
return std::nullopt;
}

if (const auto SpecializationLoc =
Expand Down Expand Up @@ -254,7 +258,7 @@ findInsertionForConstraint(const FunctionDecl *Function, ASTContext &Context) {
return utils::lexer::findPreviousTokenKind(Init->getSourceLocation(),
SM, LangOpts, tok::colon);
}
if (Constructor->init_begin() != Constructor->init_end())
if (!Constructor->inits().empty())
return std::nullopt;
}
if (Function->isDeleted()) {
Expand Down
107 changes: 107 additions & 0 deletions clang-tools-extra/clang-tidy/modernize/UseStdFormatCheck.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//===--- UseStdFormatCheck.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 "UseStdFormatCheck.h"
#include "../utils/FormatStringConverter.h"
#include "../utils/Matchers.h"
#include "../utils/OptionsUtils.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Lex/Lexer.h"
#include "clang/Tooling/FixIt.h"

using namespace clang::ast_matchers;

namespace clang::tidy::modernize {

namespace {
AST_MATCHER(StringLiteral, isOrdinary) { return Node.isOrdinary(); }
} // namespace

UseStdFormatCheck::UseStdFormatCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
StrictMode(Options.getLocalOrGlobal("StrictMode", false)),
StrFormatLikeFunctions(utils::options::parseStringList(
Options.get("StrFormatLikeFunctions", ""))),
ReplacementFormatFunction(
Options.get("ReplacementFormatFunction", "std::format")),
IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",
utils::IncludeSorter::IS_LLVM),
areDiagsSelfContained()),
MaybeHeaderToInclude(Options.get("FormatHeader")) {
if (StrFormatLikeFunctions.empty())
StrFormatLikeFunctions.push_back("absl::StrFormat");

if (!MaybeHeaderToInclude && ReplacementFormatFunction == "std::format")
MaybeHeaderToInclude = "<format>";
}

void UseStdFormatCheck::registerPPCallbacks(const SourceManager &SM,
Preprocessor *PP,
Preprocessor *ModuleExpanderPP) {
IncludeInserter.registerPreprocessor(PP);
}

void UseStdFormatCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(
callExpr(argumentCountAtLeast(1),
hasArgument(0, stringLiteral(isOrdinary())),
callee(functionDecl(unless(cxxMethodDecl()),
matchers::matchesAnyListedName(
StrFormatLikeFunctions))
.bind("func_decl")))
.bind("strformat"),
this);
}

void UseStdFormatCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
using utils::options::serializeStringList;
Options.store(Opts, "StrictMode", StrictMode);
Options.store(Opts, "StrFormatLikeFunctions",
serializeStringList(StrFormatLikeFunctions));
Options.store(Opts, "ReplacementFormatFunction", ReplacementFormatFunction);
Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle());
if (MaybeHeaderToInclude)
Options.store(Opts, "FormatHeader", *MaybeHeaderToInclude);
}

void UseStdFormatCheck::check(const MatchFinder::MatchResult &Result) {
const unsigned FormatArgOffset = 0;
const auto *OldFunction = Result.Nodes.getNodeAs<FunctionDecl>("func_decl");
const auto *StrFormat = Result.Nodes.getNodeAs<CallExpr>("strformat");

utils::FormatStringConverter::Configuration ConverterConfig;
ConverterConfig.StrictMode = StrictMode;
utils::FormatStringConverter Converter(Result.Context, StrFormat,
FormatArgOffset, ConverterConfig,
getLangOpts());
const Expr *StrFormatCall = StrFormat->getCallee();
if (!Converter.canApply()) {
diag(StrFormat->getBeginLoc(),
"unable to use '%0' instead of %1 because %2")
<< StrFormatCall->getSourceRange() << ReplacementFormatFunction
<< OldFunction->getIdentifier()
<< Converter.conversionNotPossibleReason();
return;
}

DiagnosticBuilder Diag =
diag(StrFormatCall->getBeginLoc(), "use '%0' instead of %1")
<< ReplacementFormatFunction << OldFunction->getIdentifier();
Diag << FixItHint::CreateReplacement(
CharSourceRange::getTokenRange(StrFormatCall->getSourceRange()),
ReplacementFormatFunction);
Converter.applyFixes(Diag, *Result.SourceManager);

if (MaybeHeaderToInclude)
Diag << IncludeInserter.createIncludeInsertion(
Result.Context->getSourceManager().getFileID(
StrFormatCall->getBeginLoc()),
*MaybeHeaderToInclude);
}

} // namespace clang::tidy::modernize
51 changes: 51 additions & 0 deletions clang-tools-extra/clang-tidy/modernize/UseStdFormatCheck.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//===--- UseStdFormatCheck.h - clang-tidy -----------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTDFORMATCHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTDFORMATCHECK_H

#include "../ClangTidyCheck.h"
#include "../utils/IncludeInserter.h"

namespace clang::tidy::modernize {

/// Converts calls to absl::StrFormat, or other functions via configuration
/// options, to C++20's std::format, or another function via a configuration
/// option, modifying the format string appropriately and removing
/// now-unnecessary calls to std::string::c_str() and std::string::data().
///
/// For the user-facing documentation see:
/// http://clang.llvm.org/extra/clang-tidy/checks/modernize/use-std-format.html
class UseStdFormatCheck : public ClangTidyCheck {
public:
UseStdFormatCheck(StringRef Name, ClangTidyContext *Context);
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
if (ReplacementFormatFunction == "std::format")
return LangOpts.CPlusPlus20;
return LangOpts.CPlusPlus;
}
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
Preprocessor *ModuleExpanderPP) override;
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
std::optional<TraversalKind> getCheckTraversalKind() const override {
return TK_IgnoreUnlessSpelledInSource;
}

private:
bool StrictMode;
std::vector<StringRef> StrFormatLikeFunctions;
StringRef ReplacementFormatFunction;
utils::IncludeInserter IncludeInserter;
std::optional<StringRef> MaybeHeaderToInclude;
};

} // namespace clang::tidy::modernize

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTDFORMATCHECK_H
5 changes: 4 additions & 1 deletion clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,11 @@ void UseStdPrintCheck::check(const MatchFinder::MatchResult &Result) {
FormatArgOffset = 1;
}

utils::FormatStringConverter::Configuration ConverterConfig;
ConverterConfig.StrictMode = StrictMode;
ConverterConfig.AllowTrailingNewlineRemoval = true;
utils::FormatStringConverter Converter(
Result.Context, Printf, FormatArgOffset, StrictMode, getLangOpts());
Result.Context, Printf, FormatArgOffset, ConverterConfig, getLangOpts());
const Expr *PrintfCall = Printf->getCallee();
const StringRef ReplacementFunction = Converter.usePrintNewlineFunction()
? ReplacementPrintlnFunction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ static bool containsDeclInScope(const Stmt *Node) {
}

static void removeElseAndBrackets(DiagnosticBuilder &Diag, ASTContext &Context,
const Stmt *Else, SourceLocation ElseLoc) {
const Stmt *Else, SourceLocation ElseLoc) {
auto Remap = [&](SourceLocation Loc) {
return Context.getSourceManager().getExpansionLoc(Loc);
};
Expand Down Expand Up @@ -172,7 +172,7 @@ void ElseAfterReturnCheck::registerMatchers(MatchFinder *Finder) {
breakStmt().bind(InterruptingStr), cxxThrowExpr().bind(InterruptingStr)));
Finder->addMatcher(
compoundStmt(
forEach(ifStmt(unless(isConstexpr()),
forEach(ifStmt(unless(isConstexpr()), unless(isConsteval()),
hasThen(stmt(
anyOf(InterruptsControlFlow,
compoundStmt(has(InterruptsControlFlow))))),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1358,7 +1358,7 @@ IdentifierNamingCheck::getFailureInfo(
std::replace(KindName.begin(), KindName.end(), '_', ' ');

std::string Fixup = fixupWithStyle(Type, Name, Style, HNOption, ND);
if (StringRef(Fixup).equals(Name)) {
if (StringRef(Fixup) == Name) {
if (!IgnoreFailedSplit) {
LLVM_DEBUG(Location.print(llvm::dbgs(), SM);
llvm::dbgs()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,11 @@ static bool applyAbbreviationHeuristic(
const llvm::StringMap<std::string> &AbbreviationDictionary, StringRef Arg,
StringRef Param) {
if (AbbreviationDictionary.contains(Arg) &&
Param.equals(AbbreviationDictionary.lookup(Arg)))
Param == AbbreviationDictionary.lookup(Arg))
return true;

if (AbbreviationDictionary.contains(Param) &&
Arg.equals(AbbreviationDictionary.lookup(Param)))
Arg == AbbreviationDictionary.lookup(Param))
return true;

return false;
Expand Down
18 changes: 18 additions & 0 deletions clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ Configuration files:
Checks - Same as '--checks'. Additionally, the list of
globs can be specified as a list instead of a
string.
ExcludeHeaderFilterRegex - Same as '--exclude-header-filter'.
ExtraArgs - Same as '--extra-args'.
ExtraArgsBefore - Same as '--extra-args-before'.
FormatStyle - Same as '--format-style'.
Expand Down Expand Up @@ -132,6 +133,20 @@ option in .clang-tidy file, if any.
cl::init(""),
cl::cat(ClangTidyCategory));

static cl::opt<std::string> ExcludeHeaderFilter("exclude-header-filter",
desc(R"(
Regular expression matching the names of the
headers to exclude diagnostics from. Diagnostics
from the main file of each translation unit are
always displayed.
Must be used together with --header-filter.
Can be used together with -line-filter.
This option overrides the 'ExcludeHeaderFilterRegex'
option in .clang-tidy file, if any.
)"),
cl::init(""),
cl::cat(ClangTidyCategory));

static cl::opt<bool> SystemHeaders("system-headers", desc(R"(
Display the errors from system headers.
This option overrides the 'SystemHeaders' option
Expand Down Expand Up @@ -353,6 +368,7 @@ static std::unique_ptr<ClangTidyOptionsProvider> createOptionsProvider(
DefaultOptions.Checks = DefaultChecks;
DefaultOptions.WarningsAsErrors = "";
DefaultOptions.HeaderFilterRegex = HeaderFilter;
DefaultOptions.ExcludeHeaderFilterRegex = ExcludeHeaderFilter;
DefaultOptions.SystemHeaders = SystemHeaders;
DefaultOptions.FormatStyle = FormatStyle;
DefaultOptions.User = llvm::sys::Process::GetEnv("USER");
Expand All @@ -367,6 +383,8 @@ static std::unique_ptr<ClangTidyOptionsProvider> createOptionsProvider(
OverrideOptions.WarningsAsErrors = WarningsAsErrors;
if (HeaderFilter.getNumOccurrences() > 0)
OverrideOptions.HeaderFilterRegex = HeaderFilter;
if (ExcludeHeaderFilter.getNumOccurrences() > 0)
OverrideOptions.ExcludeHeaderFilterRegex = ExcludeHeaderFilter;
if (SystemHeaders.getNumOccurrences() > 0)
OverrideOptions.SystemHeaders = SystemHeaders;
if (FormatStyle.getNumOccurrences() > 0)
Expand Down
13 changes: 13 additions & 0 deletions clang-tools-extra/clang-tidy/tool/run-clang-tidy.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,14 @@ def get_tidy_invocation(
use_color,
plugins,
warnings_as_errors,
exclude_header_filter,
):
"""Gets a command line for clang-tidy."""
start = [clang_tidy_binary]
if allow_enabling_alpha_checkers:
start.append("-allow-enabling-analyzer-alpha-checkers")
if exclude_header_filter is not None:
start.append("--exclude-header-filter=" + exclude_header_filter)
if header_filter is not None:
start.append("-header-filter=" + header_filter)
if line_filter is not None:
Expand Down Expand Up @@ -228,6 +231,7 @@ def run_tidy(args, clang_tidy_binary, tmpdir, build_path, queue, lock, failed_fi
args.use_color,
args.plugins,
args.warnings_as_errors,
args.exclude_header_filter,
)

proc = subprocess.Popen(
Expand Down Expand Up @@ -292,6 +296,14 @@ def main():
"-config option after reading specified config file. "
"Use either -config-file or -config, not both.",
)
parser.add_argument(
"-exclude-header-filter",
default=None,
help="Regular expression matching the names of the "
"headers to exclude diagnostics from. Diagnostics from "
"the main file of each translation unit are always "
"displayed.",
)
parser.add_argument(
"-header-filter",
default=None,
Expand Down Expand Up @@ -450,6 +462,7 @@ def main():
args.use_color,
args.plugins,
args.warnings_as_errors,
args.exclude_header_filter,
)
invocation.append("-list-checks")
invocation.append("-")
Expand Down
16 changes: 10 additions & 6 deletions clang-tools-extra/clang-tidy/utils/FormatStringConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -198,10 +198,11 @@ static bool castMismatchedIntegerTypes(const CallExpr *Call, bool StrictMode) {
FormatStringConverter::FormatStringConverter(ASTContext *ContextIn,
const CallExpr *Call,
unsigned FormatArgOffset,
bool StrictMode,
const Configuration ConfigIn,
const LangOptions &LO)
: Context(ContextIn),
CastMismatchedIntegerTypes(castMismatchedIntegerTypes(Call, StrictMode)),
: Context(ContextIn), Config(ConfigIn),
CastMismatchedIntegerTypes(
castMismatchedIntegerTypes(Call, ConfigIn.StrictMode)),
Args(Call->getArgs()), NumArgs(Call->getNumArgs()),
ArgsOffset(FormatArgOffset + 1), LangOpts(LO) {
assert(ArgsOffset <= NumArgs);
Expand Down Expand Up @@ -627,9 +628,12 @@ void FormatStringConverter::finalizeFormatText() {

// It's clearer to convert printf("Hello\r\n"); to std::print("Hello\r\n")
// than to std::println("Hello\r");
if (StringRef(StandardFormatString).ends_with("\\n") &&
!StringRef(StandardFormatString).ends_with("\\\\n") &&
!StringRef(StandardFormatString).ends_with("\\r\\n")) {
// Use StringRef until C++20 std::string::ends_with() is available.
const auto StandardFormatStringRef = StringRef(StandardFormatString);
if (Config.AllowTrailingNewlineRemoval &&
StandardFormatStringRef.ends_with("\\n") &&
!StandardFormatStringRef.ends_with("\\\\n") &&
!StandardFormatStringRef.ends_with("\\r\\n")) {
UsePrintNewlineFunction = true;
FormatStringNeededRewriting = true;
StandardFormatString.erase(StandardFormatString.end() - 2,
Expand Down
9 changes: 8 additions & 1 deletion clang-tools-extra/clang-tidy/utils/FormatStringConverter.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,14 @@ class FormatStringConverter
public:
using ConversionSpecifier = clang::analyze_format_string::ConversionSpecifier;
using PrintfSpecifier = analyze_printf::PrintfSpecifier;

struct Configuration {
bool StrictMode = false;
bool AllowTrailingNewlineRemoval = false;
};

FormatStringConverter(ASTContext *Context, const CallExpr *Call,
unsigned FormatArgOffset, bool StrictMode,
unsigned FormatArgOffset, Configuration Config,
const LangOptions &LO);

bool canApply() const { return ConversionNotPossibleReason.empty(); }
Expand All @@ -45,6 +51,7 @@ class FormatStringConverter

private:
ASTContext *Context;
const Configuration Config;
const bool CastMismatchedIntegerTypes;
const Expr *const *Args;
const unsigned NumArgs;
Expand Down
3 changes: 1 addition & 2 deletions clang-tools-extra/clang-tidy/utils/IncludeSorter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,7 @@ determineIncludeKind(StringRef CanonicalFile, StringRef IncludeFile,
if (FileCopy.consume_front(Parts.first) &&
FileCopy.consume_back(Parts.second)) {
// Determine the kind of this inclusion.
if (FileCopy.equals("/internal/") ||
FileCopy.equals("/proto/")) {
if (FileCopy == "/internal/" || FileCopy == "/proto/") {
return IncludeSorter::IK_MainTUInclude;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ static const NamedDecl *findDecl(const RecordDecl &RecDecl,
StringRef DeclName) {
for (const Decl *D : RecDecl.decls()) {
if (const auto *ND = dyn_cast<NamedDecl>(D)) {
if (ND->getDeclName().isIdentifier() && ND->getName().equals(DeclName))
if (ND->getDeclName().isIdentifier() && ND->getName() == DeclName)
return ND;
}
}
Expand Down
37 changes: 11 additions & 26 deletions clang-tools-extra/clangd/AST.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,11 @@ getTemplateSpecializationArgLocs(const NamedDecl &ND) {
if (const ASTTemplateArgumentListInfo *Args =
Func->getTemplateSpecializationArgsAsWritten())
return Args->arguments();
} else if (auto *Cls =
llvm::dyn_cast<ClassTemplatePartialSpecializationDecl>(&ND)) {
} else if (auto *Cls = llvm::dyn_cast<ClassTemplateSpecializationDecl>(&ND)) {
if (auto *Args = Cls->getTemplateArgsAsWritten())
return Args->arguments();
} else if (auto *Var =
llvm::dyn_cast<VarTemplatePartialSpecializationDecl>(&ND)) {
if (auto *Args = Var->getTemplateArgsAsWritten())
return Args->arguments();
} else if (auto *Var = llvm::dyn_cast<VarTemplateSpecializationDecl>(&ND)) {
if (auto *Args = Var->getTemplateArgsInfo())
if (auto *Args = Var->getTemplateArgsAsWritten())
return Args->arguments();
}
// We return std::nullopt for ClassTemplateSpecializationDecls because it does
Expand Down Expand Up @@ -270,22 +265,10 @@ std::string printTemplateSpecializationArgs(const NamedDecl &ND) {
getTemplateSpecializationArgLocs(ND)) {
printTemplateArgumentList(OS, *Args, Policy);
} else if (auto *Cls = llvm::dyn_cast<ClassTemplateSpecializationDecl>(&ND)) {
if (const TypeSourceInfo *TSI = Cls->getTypeAsWritten()) {
// ClassTemplateSpecializationDecls do not contain
// TemplateArgumentTypeLocs, they only have TemplateArgumentTypes. So we
// create a new argument location list from TypeSourceInfo.
auto STL = TSI->getTypeLoc().getAs<TemplateSpecializationTypeLoc>();
llvm::SmallVector<TemplateArgumentLoc> ArgLocs;
ArgLocs.reserve(STL.getNumArgs());
for (unsigned I = 0; I < STL.getNumArgs(); ++I)
ArgLocs.push_back(STL.getArgLoc(I));
printTemplateArgumentList(OS, ArgLocs, Policy);
} else {
// FIXME: Fix cases when getTypeAsWritten returns null inside clang AST,
// e.g. friend decls. Currently we fallback to Template Arguments without
// location information.
printTemplateArgumentList(OS, Cls->getTemplateArgs().asArray(), Policy);
}
// FIXME: Fix cases when getTypeAsWritten returns null inside clang AST,
// e.g. friend decls. Currently we fallback to Template Arguments without
// location information.
printTemplateArgumentList(OS, Cls->getTemplateArgs().asArray(), Policy);
}
OS.flush();
return TemplateArgs;
Expand Down Expand Up @@ -453,10 +436,12 @@ bool hasReservedScope(const DeclContext &DC) {
}

QualType declaredType(const TypeDecl *D) {
ASTContext &Context = D->getASTContext();
if (const auto *CTSD = llvm::dyn_cast<ClassTemplateSpecializationDecl>(D))
if (const auto *TSI = CTSD->getTypeAsWritten())
return TSI->getType();
return D->getASTContext().getTypeDeclType(D);
if (const auto *Args = CTSD->getTemplateArgsAsWritten())
return Context.getTemplateSpecializationType(
TemplateName(CTSD->getSpecializedTemplate()), Args->arguments());
return Context.getTypeDeclType(D);
}

namespace {
Expand Down
13 changes: 8 additions & 5 deletions clang-tools-extra/clangd/SemanticHighlighting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -693,17 +693,22 @@ class CollectExtraHighlightings
return true;
}

bool
VisitClassTemplateSpecializationDecl(ClassTemplateSpecializationDecl *D) {
if (auto *Args = D->getTemplateArgsAsWritten())
H.addAngleBracketTokens(Args->getLAngleLoc(), Args->getRAngleLoc());
return true;
}

bool VisitClassTemplatePartialSpecializationDecl(
ClassTemplatePartialSpecializationDecl *D) {
if (auto *TPL = D->getTemplateParameters())
H.addAngleBracketTokens(TPL->getLAngleLoc(), TPL->getRAngleLoc());
if (auto *Args = D->getTemplateArgsAsWritten())
H.addAngleBracketTokens(Args->getLAngleLoc(), Args->getRAngleLoc());
return true;
}

bool VisitVarTemplateSpecializationDecl(VarTemplateSpecializationDecl *D) {
if (auto *Args = D->getTemplateArgsInfo())
if (auto *Args = D->getTemplateArgsAsWritten())
H.addAngleBracketTokens(Args->getLAngleLoc(), Args->getRAngleLoc());
return true;
}
Expand All @@ -712,8 +717,6 @@ class CollectExtraHighlightings
VarTemplatePartialSpecializationDecl *D) {
if (auto *TPL = D->getTemplateParameters())
H.addAngleBracketTokens(TPL->getLAngleLoc(), TPL->getRAngleLoc());
if (auto *Args = D->getTemplateArgsAsWritten())
H.addAngleBracketTokens(Args->getLAngleLoc(), Args->getRAngleLoc());
return true;
}

Expand Down
3 changes: 1 addition & 2 deletions clang-tools-extra/clangd/refactor/Rename.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1090,11 +1090,10 @@ llvm::Expected<RenameResult> rename(const RenameInputs &RInputs) {
return MainFileRenameEdit.takeError();

llvm::DenseSet<Range> RenamedRanges;
if (const auto *MD = dyn_cast<ObjCMethodDecl>(&RenameDecl)) {
if (!isa<ObjCMethodDecl>(RenameDecl)) {
// TODO: Insert the ranges from the ObjCMethodDecl/ObjCMessageExpr selector
// pieces which are being renamed. This will require us to make changes to
// locateDeclAt to preserve this AST node.
} else {
RenamedRanges.insert(CurrentIdentifier);
}

Expand Down
6 changes: 6 additions & 0 deletions clang-tools-extra/clangd/unittests/SelectionTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,12 @@ TEST(SelectionTest, CommonAncestor) {
auto x = [[ns::^C<int>]];
)cpp",
"ConceptReference"},
{R"cpp(
template <typename T, typename K>
concept D = true;
template <typename T> void g(D<[[^T]]> auto abc) {}
)cpp",
"TemplateTypeParmTypeLoc"},
};

for (const Case &C : Cases) {
Expand Down
34 changes: 33 additions & 1 deletion clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ Improvements to clang-doc
Improvements to clang-query
---------------------------

The improvements are...
- Added the `file` command to dynamically load a list of commands and matchers
from an external file, allowing the cost of reading the compilation database
and building the AST to be imposed just once for faster prototyping.

Improvements to clang-rename
----------------------------
Expand All @@ -113,6 +115,9 @@ Improvements to clang-tidy
- Fixed `--verify-config` option not properly parsing checks when using the
literal operator in the `.clang-tidy` config.

- Added argument `--exclude-header-filter` and config option `ExcludeHeaderFilterRegex`
to exclude headers from analysis via a RegEx.

New checks
^^^^^^^^^^

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

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

Converts calls to ``absl::StrFormat``, or other functions via
configuration options, to C++20's ``std::format``, or another function
via a configuration option, modifying the format string appropriately and
removing now-unnecessary calls to ``std::string::c_str()`` and
``std::string::data()``.

- New :doc:`readability-enum-initial-value
<clang-tidy/checks/readability/enum-initial-value>` check.

Expand Down Expand Up @@ -202,6 +216,10 @@ Changes in existing checks
eliminating false positives resulting from direct usage of bitwise operators
within parentheses.

- Improved :doc:`bugprone-optional-value-conversion
<clang-tidy/checks/bugprone/optional-value-conversion>` check by eliminating
false positives resulting from use of optionals in unevaluated context.

- Improved :doc:`bugprone-suspicious-include
<clang-tidy/checks/bugprone/suspicious-include>` check by replacing the local
options `HeaderFileExtensions` and `ImplementationFileExtensions` by the
Expand Down Expand Up @@ -244,6 +262,12 @@ Changes in existing checks
<clang-tidy/checks/cppcoreguidelines/use-default-member-init>`. Fixed
incorrect hints when using list-initialization.

- Improved :doc:`cppcoreguidelines-special-member-functions
<clang-tidy/checks/cppcoreguidelines/special-member-functions>` check with a
new option `AllowImplicitlyDeletedCopyOrMove`, which removes the requirement
for explicit copy or move special member functions when they are already
implicitly deleted.

- Improved :doc:`google-build-namespaces
<clang-tidy/checks/google/build-namespaces>` check by replacing the local
option `HeaderFileExtensions` by the global option of the same name.
Expand Down Expand Up @@ -304,6 +328,10 @@ Changes in existing checks
don't remove parentheses used in ``sizeof`` calls when they have array index
accesses as arguments.

- Improved :doc:`modernize-use-constraints
<clang-tidy/checks/modernize/use-constraints>` check by fixing a crash that
occurred in some scenarios and excluding system headers from analysis.

- Improved :doc:`modernize-use-nullptr
<clang-tidy/checks/modernize/use-nullptr>` check to include support for C23,
which also has introduced the ``nullptr`` keyword.
Expand Down Expand Up @@ -337,6 +365,10 @@ Changes in existing checks
<clang-tidy/checks/readability/duplicate-include>` check by excluding include
directives that form the filename using macro.

- Improved :doc:`readability-else-after-return
<clang-tidy/checks/readability/else-after-return>` check to ignore
`if consteval` statements, for which the `else` branch must not be removed.

- Improved :doc:`readability-identifier-naming
<clang-tidy/checks/readability/identifier-naming>` check in `GetConfigPerFile`
mode by resolving symbolic links to header files. Fixed handling of Hungarian
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ Options

.. option:: AllowMissingMoveFunctions

When set to `true` (default is `false`), this check doesn't flag classes which define no move
operations at all. It still flags classes which define only one of either
move constructor or move assignment operator. With this option enabled, the following class won't be flagged:
When set to `true` (default is `false`), this check doesn't flag classes
which define no move operations at all. It still flags classes which define
only one of either move constructor or move assignment operator. With this
option enabled, the following class won't be flagged:

.. code-block:: c++

Expand All @@ -59,10 +60,11 @@ Options

.. option:: AllowMissingMoveFunctionsWhenCopyIsDeleted

When set to `true` (default is `false`), this check doesn't flag classes which define deleted copy
operations but don't define move operations. This flag is related to Google C++ Style Guide
https://google.github.io/styleguide/cppguide.html#Copyable_Movable_Types. With this option enabled, the
following class won't be flagged:
When set to `true` (default is `false`), this check doesn't flag classes
which define deleted copy operations but don't define move operations. This
flag is related to Google C++ Style Guide `Copyable and Movable Types
<https://google.github.io/styleguide/cppguide.html#Copyable_Movable_Types>`_.
With this option enabled, the following class won't be flagged:

.. code-block:: c++

Expand All @@ -71,3 +73,15 @@ Options
A& operator=(const A&) = delete;
~A();
};

.. option:: AllowImplicitlyDeletedCopyOrMove

When set to `true` (default is `false`), this check doesn't flag classes
which implicitly delete copy or move operations.
With this option enabled, the following class won't be flagged:

.. code-block:: c++

struct A : boost::noncopyable {
~A() { std::cout << "dtor\n"; }
};
1 change: 1 addition & 0 deletions clang-tools-extra/docs/clang-tidy/checks/list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ Clang-Tidy Checks
:doc:`modernize-use-nullptr <modernize/use-nullptr>`, "Yes"
:doc:`modernize-use-override <modernize/use-override>`, "Yes"
:doc:`modernize-use-starts-ends-with <modernize/use-starts-ends-with>`, "Yes"
:doc:`modernize-use-std-format <modernize/use-std-format>`, "Yes"
:doc:`modernize-use-std-numbers <modernize/use-std-numbers>`, "Yes"
:doc:`modernize-use-std-print <modernize/use-std-print>`, "Yes"
:doc:`modernize-use-trailing-return-type <modernize/use-trailing-return-type>`, "Yes"
Expand Down
Loading