7 changes: 0 additions & 7 deletions .github/workflows/set-release-binary-outputs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@ if echo $tag | grep -e '^[0-9a-f]\+$'; then
# This is a plain commit.
# TODO: Don't hardcode this.
release_version="18"
build_dir="$tag"
upload='false'
ref="$tag"
flags="-git-ref $tag -test-asserts"

else

Expand All @@ -30,12 +28,7 @@ else
fi
release_version=`echo "$tag" | sed 's/llvmorg-//g'`
release=`echo "$release_version" | sed 's/-.*//g'`
build_dir=`echo "$release_version" | sed 's,^[^-]\+,final,' | sed 's,[^-]\+-rc\(.\+\),rc\1,'`
rc_flags=`echo "$release_version" | sed 's,^[^-]\+,-final,' | sed 's,[^-]\+-rc\(.\+\),-rc \1 -test-asserts,' | sed 's,--,-,'`
flags="-release $release $rc_flags"
fi
echo "release-version=$release_version" >> $GITHUB_OUTPUT
echo "build-dir=$build_dir" >> $GITHUB_OUTPUT
echo "flags=$flags" >> $GITHUB_OUTPUT
echo "upload=$upload" >> $GITHUB_OUTPUT
echo "ref=$tag" >> $GITHUB_OUTPUT
4 changes: 4 additions & 0 deletions bolt/include/bolt/Core/BinaryContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "bolt/Core/JumpTable.h"
#include "bolt/Core/MCPlusBuilder.h"
#include "bolt/RuntimeLibs/RuntimeLibrary.h"
#include "llvm/ADT/AddressRanges.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/iterator.h"
Expand Down Expand Up @@ -726,6 +727,9 @@ class BinaryContext {
uint64_t OldTextSectionOffset{0};
uint64_t OldTextSectionSize{0};

/// Area in the input binary reserved for BOLT.
AddressRange BOLTReserved;

/// Address of the code/function that is executed before any other code in
/// the binary.
std::optional<uint64_t> StartFunctionAddress;
Expand Down
2 changes: 2 additions & 0 deletions bolt/include/bolt/Core/BinaryFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,8 @@ class BinaryFunction {
return const_cast<BinaryFunction *>(this)->getInstructionAtOffset(Offset);
}

std::optional<MCInst> disassembleInstructionAtOffset(uint64_t Offset) const;

/// Return offset for the first instruction. If there is data at the
/// beginning of a function then offset of the first instruction could
/// be different from 0
Expand Down
4 changes: 0 additions & 4 deletions bolt/include/bolt/Passes/FrameAnalysis.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,6 @@ class FrameAnalysis {
std::unique_ptr<StackPointerTracking>>
SPTMap;

/// A vector that stores ids of the allocators that are used in SPT
/// computation
std::vector<MCPlusBuilder::AllocatorIdTy> SPTAllocatorsId;

public:
explicit FrameAnalysis(BinaryContext &BC, BinaryFunctionCallGraph &CG);

Expand Down
2 changes: 1 addition & 1 deletion bolt/include/bolt/Passes/IndirectCallPromotion.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ class IndirectCallPromotion : public BinaryFunctionPass {
struct Location {
MCSymbol *Sym{nullptr};
uint64_t Addr{0};
bool isValid() const { return Sym || (!Sym && Addr != 0); }
bool isValid() const { return Sym || Addr != 0; }
Location() {}
explicit Location(MCSymbol *Sym) : Sym(Sym) {}
explicit Location(uint64_t Addr) : Addr(Addr) {}
Expand Down
8 changes: 1 addition & 7 deletions bolt/include/bolt/Profile/DataAggregator.h
Original file line number Diff line number Diff line change
Expand Up @@ -198,14 +198,8 @@ class DataAggregator : public DataReader {
/// A trace is region of code executed between two LBR entries supplied in
/// execution order.
///
/// Return true if the trace is valid, false otherwise.
bool
recordTrace(BinaryFunction &BF, const LBREntry &First, const LBREntry &Second,
uint64_t Count,
SmallVector<std::pair<uint64_t, uint64_t>, 16> &Branches) const;

/// Return a vector of offsets corresponding to a trace in a function
/// (see recordTrace() above).
/// if the trace is valid, std::nullopt otherwise.
std::optional<SmallVector<std::pair<uint64_t, uint64_t>, 16>>
getFallthroughsInTrace(BinaryFunction &BF, const LBREntry &First,
const LBREntry &Second, uint64_t Count = 1) const;
Expand Down
7 changes: 0 additions & 7 deletions bolt/include/bolt/Rewrite/DWARFRewriter.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,13 +177,6 @@ class DWARFRewriter {
DIEValue &HighPCAttrInfo,
std::optional<uint64_t> RangesBase = std::nullopt);

/// 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.
void addStringHelper(DIEBuilder &DIEBldr, DIE &Die, const DWARFUnit &Unit,
DIEValue &DIEAttrInfo, StringRef Str);

public:
DWARFRewriter(BinaryContext &BC) : BC(BC) {}

Expand Down
4 changes: 4 additions & 0 deletions bolt/include/bolt/Rewrite/RewriteInstance.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ class RewriteInstance {
/// from meta data in the file.
void discoverFileObjects();

/// Check if the input binary has a space reserved for BOLT and use it for new
/// section allocations if found.
void discoverBOLTReserved();

/// Check whether we should use DT_FINI or DT_FINI_ARRAY for instrumentation.
/// DT_FINI is preferred; DT_FINI_ARRAY is only used when no DT_FINI entry was
/// found.
Expand Down
15 changes: 15 additions & 0 deletions bolt/lib/Core/BinaryFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1167,6 +1167,21 @@ void BinaryFunction::handleAArch64IndirectCall(MCInst &Instruction,
}
}

std::optional<MCInst>
BinaryFunction::disassembleInstructionAtOffset(uint64_t Offset) const {
assert(CurrentState == State::Empty && "Function should not be disassembled");
assert(Offset < MaxSize && "Invalid offset");
ErrorOr<ArrayRef<unsigned char>> FunctionData = getData();
assert(FunctionData && "Cannot get function as data");
MCInst Instr;
uint64_t InstrSize = 0;
const uint64_t InstrAddress = getAddress() + Offset;
if (BC.DisAsm->getInstruction(Instr, InstrSize, FunctionData->slice(Offset),
InstrAddress, nulls()))
return Instr;
return std::nullopt;
}

Error BinaryFunction::disassemble() {
NamedRegionTimer T("disassemble", "Disassemble function", "buildfuncs",
"Build Binary Functions", opts::TimeBuild);
Expand Down
29 changes: 15 additions & 14 deletions bolt/lib/Core/ParallelUtilities.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,20 @@ void runOnEachFunctionWithUniqueAllocId(
LLVM_DEBUG(T.stopTimer());
};

unsigned AllocId = 1;
auto EnsureAllocatorExists = [&BC](unsigned AllocId) {
if (!BC.MIB->checkAllocatorExists(AllocId)) {
MCPlusBuilder::AllocatorIdTy Id =
BC.MIB->initializeNewAnnotationAllocator();
(void)Id;
assert(AllocId == Id && "unexpected allocator id created");
}
};

if (opts::NoThreads || ForceSequential) {
runBlock(BC.getBinaryFunctions().begin(), BC.getBinaryFunctions().end(), 0);
EnsureAllocatorExists(AllocId);
runBlock(BC.getBinaryFunctions().begin(), BC.getBinaryFunctions().end(),
AllocId);
return;
}
// This lock is used to postpone task execution
Expand All @@ -205,32 +217,21 @@ void runOnEachFunctionWithUniqueAllocId(
ThreadPoolInterface &Pool = getThreadPool();
auto BlockBegin = BC.getBinaryFunctions().begin();
unsigned CurrentCost = 0;
unsigned AllocId = 1;
for (auto It = BC.getBinaryFunctions().begin();
It != BC.getBinaryFunctions().end(); ++It) {
BinaryFunction &BF = It->second;
CurrentCost += computeCostFor(BF, SkipPredicate, SchedPolicy);

if (CurrentCost >= BlockCost) {
if (!BC.MIB->checkAllocatorExists(AllocId)) {
MCPlusBuilder::AllocatorIdTy Id =
BC.MIB->initializeNewAnnotationAllocator();
(void)Id;
assert(AllocId == Id && "unexpected allocator id created");
}
EnsureAllocatorExists(AllocId);
Pool.async(runBlock, BlockBegin, std::next(It), AllocId);
AllocId++;
BlockBegin = std::next(It);
CurrentCost = 0;
}
}

if (!BC.MIB->checkAllocatorExists(AllocId)) {
MCPlusBuilder::AllocatorIdTy Id =
BC.MIB->initializeNewAnnotationAllocator();
(void)Id;
assert(AllocId == Id && "unexpected allocator id created");
}
EnsureAllocatorExists(AllocId);

Pool.async(runBlock, BlockBegin, BC.getBinaryFunctions().end(), AllocId);
Lock.unlock();
Expand Down
8 changes: 8 additions & 0 deletions bolt/lib/Passes/BinaryPasses.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,9 @@ static uint64_t fixDoubleJumps(BinaryFunction &Function, bool MarkInvalid) {
Pred->removeSuccessor(&BB);
Pred->eraseInstruction(Pred->findInstruction(Branch));
Pred->addTailCallInstruction(SuccSym);
MCInst *TailCall = Pred->getLastNonPseudoInstr();
assert(TailCall);
MIB->setOffset(*TailCall, BB.getOffset());
} else {
return false;
}
Expand Down Expand Up @@ -910,6 +913,11 @@ uint64_t SimplifyConditionalTailCalls::fixTailCalls(BinaryFunction &BF) {
auto &CTCAnnotation =
MIB->getOrCreateAnnotationAs<uint64_t>(*CondBranch, "CTCTakenCount");
CTCAnnotation = CTCTakenFreq;
// Preserve Offset annotation, used in BAT.
// Instr is a direct tail call instruction that was created when CTCs are
// first expanded, and has the original CTC offset set.
if (std::optional<uint32_t> Offset = MIB->getOffset(*Instr))
MIB->setOffset(*CondBranch, *Offset);

// Remove the unused successor which may be eliminated later
// if there are no other users.
Expand Down
5 changes: 0 additions & 5 deletions bolt/lib/Passes/FrameAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -561,11 +561,6 @@ FrameAnalysis::FrameAnalysis(BinaryContext &BC, BinaryFunctionCallGraph &CG)
NamedRegionTimer T1("clearspt", "clear spt", "FA", "FA breakdown",
opts::TimeFA);
clearSPTMap();

// Clean up memory allocated for annotation values
if (!opts::NoThreads)
for (MCPlusBuilder::AllocatorIdTy Id : SPTAllocatorsId)
BC.MIB->freeValuesAllocator(Id);
}
}

Expand Down
13 changes: 13 additions & 0 deletions bolt/lib/Passes/SplitFunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,12 @@ Error SplitFunctions::runOnFunctions(BinaryContext &BC) {
if (!opts::SplitFunctions)
return Error::success();

if (BC.IsLinuxKernel && BC.BOLTReserved.empty()) {
BC.errs() << "BOLT-ERROR: split functions require reserved space in the "
"Linux kernel binary\n";
exit(1);
}

// If split strategy is not CDSplit, then a second run of the pass is not
// needed after function reordering.
if (BC.HasFinalizedFunctionOrder &&
Expand Down Expand Up @@ -829,6 +835,13 @@ void SplitFunctions::splitFunction(BinaryFunction &BF, SplitStrategy &S) {
}
}
}

// Outlining blocks with dynamic branches is not supported yet.
if (BC.IsLinuxKernel) {
if (llvm::any_of(
*BB, [&](MCInst &Inst) { return BC.MIB->isDynamicBranch(Inst); }))
BB->setCanOutline(false);
}
}

BF.getLayout().updateLayoutIndices();
Expand Down
85 changes: 53 additions & 32 deletions bolt/lib/Profile/DataAggregator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/Errc.h"
#include "llvm/Support/FileSystem.h"
Expand Down Expand Up @@ -773,9 +774,19 @@ bool DataAggregator::doInterBranch(BinaryFunction *FromFunc,

bool DataAggregator::doBranch(uint64_t From, uint64_t To, uint64_t Count,
uint64_t Mispreds) {
bool IsReturn = false;
auto handleAddress = [&](uint64_t &Addr, bool IsFrom) -> BinaryFunction * {
if (BinaryFunction *Func = getBinaryFunctionContainingAddress(Addr)) {
Addr -= Func->getAddress();
if (IsFrom) {
auto checkReturn = [&](auto MaybeInst) {
IsReturn = MaybeInst && BC->MIB->isReturn(*MaybeInst);
};
if (Func->hasInstructions())
checkReturn(Func->getInstructionAtOffset(Addr));
else
checkReturn(Func->disassembleInstructionAtOffset(Addr));
}

if (BAT)
Addr = BAT->translate(Func->getAddress(), Addr, IsFrom);
Expand All @@ -792,6 +803,9 @@ bool DataAggregator::doBranch(uint64_t From, uint64_t To, uint64_t Count,
};

BinaryFunction *FromFunc = handleAddress(From, /*IsFrom=*/true);
// Ignore returns.
if (IsReturn)
return true;
BinaryFunction *ToFunc = handleAddress(To, /*IsFrom=*/false);
if (!FromFunc && !ToFunc)
return false;
Expand Down Expand Up @@ -861,14 +875,17 @@ bool DataAggregator::doTrace(const LBREntry &First, const LBREntry &Second,
return true;
}

bool DataAggregator::recordTrace(
BinaryFunction &BF, const LBREntry &FirstLBR, const LBREntry &SecondLBR,
uint64_t Count,
SmallVector<std::pair<uint64_t, uint64_t>, 16> &Branches) const {
std::optional<SmallVector<std::pair<uint64_t, uint64_t>, 16>>
DataAggregator::getFallthroughsInTrace(BinaryFunction &BF,
const LBREntry &FirstLBR,
const LBREntry &SecondLBR,
uint64_t Count) const {
SmallVector<std::pair<uint64_t, uint64_t>, 16> Branches;

BinaryContext &BC = BF.getBinaryContext();

if (!BF.isSimple())
return false;
return std::nullopt;

assert(BF.hasCFG() && "can only record traces in CFG state");

Expand All @@ -877,13 +894,13 @@ bool DataAggregator::recordTrace(
const uint64_t To = SecondLBR.From - BF.getAddress();

if (From > To)
return false;
return std::nullopt;

const BinaryBasicBlock *FromBB = BF.getBasicBlockContainingOffset(From);
const BinaryBasicBlock *ToBB = BF.getBasicBlockContainingOffset(To);

if (!FromBB || !ToBB)
return false;
return std::nullopt;

// Adjust FromBB if the first LBR is a return from the last instruction in
// the previous block (that instruction should be a call).
Expand All @@ -907,7 +924,7 @@ bool DataAggregator::recordTrace(
// within the same basic block, e.g. when two call instructions are in the
// same block. In this case we skip the processing.
if (FromBB == ToBB)
return true;
return Branches;

// Process blocks in the original layout order.
BinaryBasicBlock *BB = BF.getLayout().getBlock(FromBB->getIndex());
Expand All @@ -921,7 +938,7 @@ bool DataAggregator::recordTrace(
LLVM_DEBUG(dbgs() << "no fall-through for the trace:\n"
<< " " << FirstLBR << '\n'
<< " " << SecondLBR << '\n');
return false;
return std::nullopt;
}

const MCInst *Instr = BB->getLastNonPseudoInstr();
Expand All @@ -945,20 +962,7 @@ bool DataAggregator::recordTrace(
BI.Count += Count;
}

return true;
}

std::optional<SmallVector<std::pair<uint64_t, uint64_t>, 16>>
DataAggregator::getFallthroughsInTrace(BinaryFunction &BF,
const LBREntry &FirstLBR,
const LBREntry &SecondLBR,
uint64_t Count) const {
SmallVector<std::pair<uint64_t, uint64_t>, 16> Res;

if (!recordTrace(BF, FirstLBR, SecondLBR, Count, Res))
return std::nullopt;

return Res;
return Branches;
}

bool DataAggregator::recordEntry(BinaryFunction &BF, uint64_t To, bool Mispred,
Expand Down Expand Up @@ -1996,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 @@ -2336,7 +2340,7 @@ std::error_code DataAggregator::writeBATYAML(BinaryContext &BC,
continue;
BinaryFunction *BF = BC.getBinaryFunctionAtAddress(FuncAddress);
assert(BF);
YamlBF.Name = FuncName.str();
YamlBF.Name = getLocationName(*BF);
YamlBF.Id = BF->getFunctionNumber();
YamlBF.Hash = BAT->getBFHash(FuncAddress);
YamlBF.ExecCount = BF->getKnownExecutionCount();
Expand Down Expand Up @@ -2375,21 +2379,27 @@ std::error_code DataAggregator::writeBATYAML(BinaryContext &BC,
return CSI;
};

// Lookup containing basic block offset and index
auto getBlock = [&BlockMap](uint32_t Offset) {
auto BlockIt = BlockMap.upper_bound(Offset);
if (LLVM_UNLIKELY(BlockIt == BlockMap.begin())) {
errs() << "BOLT-ERROR: invalid BAT section\n";
exit(1);
}
--BlockIt;
return std::pair(BlockIt->first, BlockIt->second.getBBIndex());
};

for (const auto &[FromOffset, SuccKV] : Branches.IntraIndex) {
if (!BlockMap.isInputBlock(FromOffset))
continue;
const unsigned Index = BlockMap.getBBIndex(FromOffset);
const auto &[_, Index] = getBlock(FromOffset);
yaml::bolt::BinaryBasicBlockProfile &YamlBB = YamlBF.Blocks[Index];
for (const auto &[SuccOffset, SuccDataIdx] : SuccKV)
if (BlockMap.isInputBlock(SuccOffset))
YamlBB.Successors.emplace_back(
getSuccessorInfo(SuccOffset, SuccDataIdx));
}
for (const auto &[FromOffset, CallTo] : Branches.InterIndex) {
auto BlockIt = BlockMap.upper_bound(FromOffset);
--BlockIt;
const unsigned BlockOffset = BlockIt->first;
const unsigned BlockIndex = BlockIt->second.getBBIndex();
const auto &[BlockOffset, BlockIndex] = getBlock(FromOffset);
yaml::bolt::BinaryBasicBlockProfile &YamlBB = YamlBF.Blocks[BlockIndex];
const uint32_t Offset = FromOffset - BlockOffset;
for (const auto &[CallToLoc, CallToIdx] : CallTo)
Expand All @@ -2400,6 +2410,17 @@ std::error_code DataAggregator::writeBATYAML(BinaryContext &BC,
return A.Offset < B.Offset;
});
}
// Set entry counts, similar to DataReader::readProfile.
for (const llvm::bolt::BranchInfo &BI : Branches.EntryData) {
if (!BlockMap.isInputBlock(BI.To.Offset)) {
if (opts::Verbosity >= 1)
errs() << "BOLT-WARNING: Unexpected EntryData in " << FuncName
<< " at 0x" << Twine::utohexstr(BI.To.Offset) << '\n';
continue;
}
const unsigned BlockIndex = BlockMap.getBBIndex(BI.To.Offset);
YamlBF.Blocks[BlockIndex].ExecCount += BI.Branches;
}
// Drop blocks without a hash, won't be useful for stale matching.
llvm::erase_if(YamlBF.Blocks,
[](const yaml::bolt::BinaryBasicBlockProfile &YamlBB) {
Expand Down
3 changes: 1 addition & 2 deletions bolt/lib/Profile/DataReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1205,8 +1205,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
90 changes: 50 additions & 40 deletions bolt/lib/Rewrite/DWARFRewriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -582,19 +582,51 @@ static void emitDWOBuilder(const std::string &DWOName,
Rewriter.writeDWOFiles(CU, OverriddenSections, DWOName, LocWriter);
}

void DWARFRewriter::addStringHelper(DIEBuilder &DIEBldr, DIE &Die,
const DWARFUnit &Unit,
DIEValue &DIEAttrInfo, StringRef Str) {
uint32_t NewOffset = StrWriter->addString(Str);
/// 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);
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;
}

using DWARFUnitVec = std::vector<DWARFUnit *>;
using CUPartitionVector = std::vector<DWARFUnitVec>;
/// Partitions CUs in to buckets. Bucket size is controlled by
Expand Down Expand Up @@ -692,33 +724,6 @@ void DWARFRewriter::updateDebugInfo() {
// specified.
std::unordered_map<std::string, uint32_t> NameToIndexMap;

auto updateDWONameCompDir = [&](DWARFUnit &Unit, DIEBuilder &DIEBldr,
DIE &UnitDIE) -> std::string {
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;

{
std::lock_guard<std::mutex> Lock(AccessMutex);
ObjectName = getDWOName(Unit, NameToIndexMap);
}
addStringHelper(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(DIEBldr, UnitDIE, Unit, CompDirAttrInfo,
opts::DwarfOutputPath.c_str());
}
return ObjectName;
};

DWARF5AcceleratorTable DebugNamesTable(opts::CreateDebugNames, BC,
*StrWriter);
DWPState State;
Expand All @@ -741,8 +746,13 @@ void DWARFRewriter::updateDebugInfo() {
DIEBuilder DWODIEBuilder(BC, &(*SplitCU)->getContext(), DebugNamesTable,
Unit);
DWODIEBuilder.buildDWOUnit(**SplitCU);
std::string DWOName = updateDWONameCompDir(
*Unit, *DIEBlder, *DIEBlder->getUnitDIEbyUnit(*Unit));
std::string DWOName = "";
{
std::lock_guard<std::mutex> Lock(AccessMutex);
DWOName = updateDWONameCompDir(*StrOffstsWriter, *StrWriter,
NameToIndexMap, *Unit, *DIEBlder,
*DIEBlder->getUnitDIEbyUnit(*Unit));
}

DebugLoclistWriter DebugLocDWoWriter(*Unit, Unit->getVersion(), true);
DebugRangesSectionWriter *TempRangesSectionWriter = RangesSectionWriter;
Expand Down Expand Up @@ -1540,7 +1550,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 @@ -1623,10 +1633,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 @@ -1761,7 +1771,7 @@ 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";
return SectionContents;
Expand Down Expand Up @@ -1949,7 +1959,7 @@ void DWARFRewriter::updateDWP(DWARFUnit &CU,
continue;
}

if (SectionName.equals("debug_str.dwo")) {
if (SectionName == "debug_str.dwo") {
CurStrSection = OutData;
} else {
// Since handleDebugDataPatching returned true, we already know this is
Expand Down
274 changes: 149 additions & 125 deletions bolt/lib/Rewrite/LinuxKernelRewriter.cpp

Large diffs are not rendered by default.

82 changes: 44 additions & 38 deletions bolt/lib/Rewrite/RewriteInstance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1347,6 +1347,35 @@ void RewriteInstance::discoverFileObjects() {

registerFragments();
FileSymbols.clear();

discoverBOLTReserved();
}

void RewriteInstance::discoverBOLTReserved() {
BinaryData *StartBD = BC->getBinaryDataByName(getBOLTReservedStart());
BinaryData *EndBD = BC->getBinaryDataByName(getBOLTReservedEnd());
if (!StartBD != !EndBD) {
BC->errs() << "BOLT-ERROR: one of the symbols is missing from the binary: "
<< getBOLTReservedStart() << ", " << getBOLTReservedEnd()
<< '\n';
exit(1);
}

if (!StartBD)
return;

if (StartBD->getAddress() >= EndBD->getAddress()) {
BC->errs() << "BOLT-ERROR: invalid reserved space boundaries\n";
exit(1);
}
BC->BOLTReserved = AddressRange(StartBD->getAddress(), EndBD->getAddress());
BC->outs() << "BOLT-INFO: using reserved space for allocating new sections\n";

PHDRTableOffset = 0;
PHDRTableAddress = 0;
NewTextSegmentAddress = 0;
NewTextSegmentOffset = 0;
NextAvailableAddress = BC->BOLTReserved.start();
}

Error RewriteInstance::discoverRtFiniAddress() {
Expand Down Expand Up @@ -3617,26 +3646,6 @@ void RewriteInstance::updateMetadata() {
void RewriteInstance::mapFileSections(BOLTLinker::SectionMapper MapSection) {
BC->deregisterUnusedSections();

// Check if the input has a space reserved for BOLT.
BinaryData *StartBD = BC->getBinaryDataByName(getBOLTReservedStart());
BinaryData *EndBD = BC->getBinaryDataByName(getBOLTReservedEnd());
if (!StartBD != !EndBD) {
BC->errs() << "BOLT-ERROR: one of the symbols is missing from the binary: "
<< getBOLTReservedStart() << ", " << getBOLTReservedEnd()
<< '\n';
exit(1);
}

if (StartBD) {
PHDRTableOffset = 0;
PHDRTableAddress = 0;
NewTextSegmentAddress = 0;
NewTextSegmentOffset = 0;
NextAvailableAddress = StartBD->getAddress();
BC->outs()
<< "BOLT-INFO: using reserved space for allocating new sections\n";
}

// If no new .eh_frame was written, remove relocated original .eh_frame.
BinarySection *RelocatedEHFrameSection =
getSection(".relocated" + getEHFrameSectionName());
Expand All @@ -3657,12 +3666,12 @@ void RewriteInstance::mapFileSections(BOLTLinker::SectionMapper MapSection) {
// Map the rest of the sections.
mapAllocatableSections(MapSection);

if (StartBD) {
const uint64_t ReservedSpace = EndBD->getAddress() - StartBD->getAddress();
const uint64_t AllocatedSize = NextAvailableAddress - StartBD->getAddress();
if (ReservedSpace < AllocatedSize) {
BC->errs() << "BOLT-ERROR: reserved space (" << ReservedSpace << " byte"
<< (ReservedSpace == 1 ? "" : "s")
if (!BC->BOLTReserved.empty()) {
const uint64_t AllocatedSize =
NextAvailableAddress - BC->BOLTReserved.start();
if (BC->BOLTReserved.size() < AllocatedSize) {
BC->errs() << "BOLT-ERROR: reserved space (" << BC->BOLTReserved.size()
<< " byte" << (BC->BOLTReserved.size() == 1 ? "" : "s")
<< ") is smaller than required for new allocations ("
<< AllocatedSize << " bytes)\n";
exit(1);
Expand Down Expand Up @@ -4047,6 +4056,7 @@ void RewriteInstance::patchELFPHDRTable() {
NewWritableSegmentSize = NextAvailableAddress - NewWritableSegmentAddress;
}

const uint64_t SavedPos = OS.tell();
OS.seek(PHDRTableOffset);

auto createNewTextPhdr = [&]() {
Expand Down Expand Up @@ -4151,6 +4161,8 @@ void RewriteInstance::patchELFPHDRTable() {
<< "BOLT-ERROR: could not find PT_GNU_STACK program header to modify\n";
exit(1);
}

OS.seek(SavedPos);
}

namespace {
Expand Down Expand Up @@ -5041,10 +5053,6 @@ void RewriteInstance::patchELFSymTabs(ELFObjectFile<ELFT> *File) {
assert((DynSymSection || BC->IsStaticExecutable) &&
"dynamic symbol table expected");
if (DynSymSection) {
// Set pointer to the end of the section, so we can use pwrite to update
// the dynamic symbol table.
Out->os().seek(DynSymSection->sh_offset + DynSymSection->sh_size);

updateELFSymbolTable(
File,
/*IsDynSym=*/true,
Expand Down Expand Up @@ -5853,13 +5861,11 @@ void RewriteInstance::writeEHFrameHeader() {

NextAvailableAddress += EHFrameHdrSec.getOutputSize();

if (const BinaryData *ReservedEnd =
BC->getBinaryDataByName(getBOLTReservedEnd())) {
if (NextAvailableAddress > ReservedEnd->getAddress()) {
BC->errs() << "BOLT-ERROR: unable to fit " << getEHFrameHdrSectionName()
<< " into reserved space\n";
exit(1);
}
if (!BC->BOLTReserved.empty() &&
(NextAvailableAddress > BC->BOLTReserved.end())) {
BC->errs() << "BOLT-ERROR: unable to fit " << getEHFrameHdrSectionName()
<< " into reserved space\n";
exit(1);
}

// Merge new .eh_frame with the relocated original so that gdb can locate all
Expand Down Expand Up @@ -5893,7 +5899,7 @@ uint64_t RewriteInstance::getNewValueForSymbol(const StringRef Name) {

uint64_t RewriteInstance::getFileOffsetForAddress(uint64_t Address) const {
// Check if it's possibly part of the new segment.
if (Address >= NewTextSegmentAddress)
if (NewTextSegmentAddress && Address >= NewTextSegmentAddress)
return Address - NewTextSegmentAddress + NewTextSegmentOffset;

// Find an existing segment that matches the address.
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
1 change: 1 addition & 0 deletions bolt/test/X86/Inputs/blarge_new_bat_branchentry.preagg.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
B 80010c 800194 1 0
35 changes: 35 additions & 0 deletions bolt/test/X86/Inputs/jump-table-fixed-ref-pic.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
.globl main
.type main, %function
main:
.cfi_startproc
cmpq $0x3, %rdi
jae .L4
cmpq $0x1, %rdi
jne .L4
mov .Ljt_pic+8(%rip), %rax
lea .Ljt_pic(%rip), %rdx
add %rdx, %rax
jmpq *%rax
.L1:
movq $0x1, %rax
jmp .L5
.L2:
movq $0x0, %rax
jmp .L5
.L3:
movq $0x2, %rax
jmp .L5
.L4:
mov $0x3, %rax
.L5:
retq
.cfi_endproc

.section .rodata
.align 16
.Ljt_pic:
.long .L1 - .Ljt_pic
.long .L2 - .Ljt_pic
.long .L3 - .Ljt_pic
.long .L4 - .Ljt_pic

6 changes: 3 additions & 3 deletions bolt/test/X86/bb-with-two-tail-calls.s
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
# RUN: llvm-strip --strip-unneeded %t.o
# RUN: %clang %cflags %t.o -o %t.exe -Wl,-q -nostdlib
# RUN: llvm-bolt %t.exe -o %t.out --data %t.fdata --lite=0 --dyno-stats \
# RUN: --print-sctc --print-only=_start 2>&1 | FileCheck %s
# RUN: --print-sctc --print-only=_start -enable-bat 2>&1 | FileCheck %s
# CHECK-NOT: Assertion `BranchInfo.size() == 2 && "could only be called for blocks with 2 successors"' failed.
# Two tail calls in the same basic block after SCTC:
# CHECK: {{.*}}: ja {{.*}} # TAILCALL # CTCTakenCount: {{.*}}
# CHECK-NEXT: {{.*}}: jmp {{.*}} # TAILCALL
# CHECK: {{.*}}: ja {{.*}} # TAILCALL # Offset: 7 # CTCTakenCount: 4
# CHECK-NEXT: {{.*}}: jmp {{.*}} # TAILCALL # Offset: 12

.globl _start
_start:
Expand Down
20 changes: 18 additions & 2 deletions bolt/test/X86/bolt-address-translation-yaml.test
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ RUN: llvm-bolt %t.exe -o %t.out --pa -p %p/Inputs/blarge_new.preagg.txt \
RUN: --reorder-blocks=ext-tsp --split-functions --split-strategy=cdsplit \
RUN: --reorder-functions=cdsort --enable-bat --dyno-stats --skip-funcs=main \
RUN: 2>&1 | FileCheck --check-prefix WRITE-BAT-CHECK %s
# Check that branch with entry in BAT is accounted for.
RUN: perf2bolt %t.out --pa -p %p/Inputs/blarge_new_bat_branchentry.preagg.txt \
RUN: -w %t.yaml -o %t.fdata
RUN: llvm-bolt %t.exe -data %t.fdata -w %t.yaml-fdata -o %t.null
RUN: FileCheck --input-file %t.yaml --check-prefix BRANCHENTRY-YAML-CHECK %s
RUN: FileCheck --input-file %t.yaml-fdata --check-prefix BRANCHENTRY-YAML-CHECK %s
BRANCHENTRY-YAML-CHECK: - name: SolveCubic
BRANCHENTRY-YAML-CHECK: bid: 0
BRANCHENTRY-YAML-CHECK: hash: 0x700F19D24600000
BRANCHENTRY-YAML-CHECK-NEXT: succ: [ { bid: 7, cnt: 1 }
# Large profile test
RUN: perf2bolt %t.out --pa -p %p/Inputs/blarge_new_bat.preagg.txt -w %t.yaml -o %t.fdata \
RUN: 2>&1 | FileCheck --check-prefix READ-BAT-CHECK %s
RUN: FileCheck --input-file %t.yaml --check-prefix YAML-BAT-CHECK %s
Expand All @@ -13,7 +24,7 @@ RUN: llvm-bolt %t.exe -data %t.fdata -w %t.yaml-fdata -o /dev/null
RUN: FileCheck --input-file %t.yaml-fdata --check-prefix YAML-BAT-CHECK %s

# Test resulting YAML profile with the original binary (no-stale mode)
RUN: llvm-bolt %t.exe -data %t.yaml -o %t.null -dyno-stats \
RUN: llvm-bolt %t.exe -data %t.yaml -o %t.null -dyno-stats 2>&1 \
RUN: | FileCheck --check-prefix CHECK-BOLT-YAML %s

WRITE-BAT-CHECK: BOLT-INFO: Wrote 5 BAT maps
Expand Down Expand Up @@ -48,6 +59,10 @@ YAML-BAT-CHECK-NEXT: hash: 0x6AF7E61EA3966722
YAML-BAT-CHECK-NEXT: exec: 25
YAML-BAT-CHECK-NEXT: nblocks: 15
YAML-BAT-CHECK-NEXT: blocks:
YAML-BAT-CHECK-NEXT: - bid: 0
YAML-BAT-CHECK-NEXT: insns: [[#]]
YAML-BAT-CHECK-NEXT: hash: 0x700F19D24600000
YAML-BAT-CHECK-NEXT: exec: 25
YAML-BAT-CHECK: - bid: 3
YAML-BAT-CHECK-NEXT: insns: [[#]]
YAML-BAT-CHECK-NEXT: hash: 0xDDA1DC5F69F900AC
Expand All @@ -63,7 +78,8 @@ YAML-BAT-CHECK-NEXT: blocks:
YAML-BAT-CHECK: - bid: 1
YAML-BAT-CHECK-NEXT: insns: [[#]]
YAML-BAT-CHECK-NEXT: hash: 0xD70DC695320E0010
YAML-BAT-CHECK-NEXT: succ: {{.*}} { bid: 2, cnt: [[#]] }
YAML-BAT-CHECK-NEXT: succ: {{.*}} { bid: 2, cnt: [[#]]

CHECK-BOLT-YAML: pre-processing profile using YAML profile reader
CHECK-BOLT-YAML-NEXT: 5 out of 16 functions in the binary (31.2%) have non-empty execution profile
CHECK-BOLT-YAML-NOT: invalid (possibly stale) profile
9 changes: 9 additions & 0 deletions bolt/test/X86/jump-table-fixed-ref-pic.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Verify that BOLT detects fixed destination of indirect jump for PIC
# case.

XFAIL: *

RUN: %clang %cflags -no-pie %S/Inputs/jump-table-fixed-ref-pic.s -Wl,-q -o %t
RUN: llvm-bolt %t --relocs -o %t.null 2>&1 | FileCheck %s

CHECK: BOLT-INFO: fixed indirect branch detected in main
40 changes: 40 additions & 0 deletions bolt/test/X86/linux-smp-locks.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# REQUIRES: system-linux

## Check that BOLT correctly parses and updates the Linux kernel .smp_locks
## section.

# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-unknown %s -o %t.o
# RUN: %clang %cflags -nostdlib %t.o -o %t.exe \
# RUN: -Wl,--image-base=0xffffffff80000000,--no-dynamic-linker,--no-eh-frame-hdr,--no-pie
# RUN: llvm-bolt %t.exe --print-normalized --keep-nops=0 --bolt-info=0 -o %t.out \
# RUN: |& FileCheck %s

## Check the output of BOLT with NOPs removed.

# RUN: llvm-bolt %t.out -o %t.out.1 --print-normalized |& FileCheck %s

# CHECK: BOLT-INFO: Linux kernel binary detected
# CHECK: BOLT-INFO: parsed 2 SMP lock entries

.text
.globl _start
.type _start, %function
_start:
nop
nop
.L0:
lock incl (%rdi)
# CHECK: lock {{.*}} SMPLock
.L1:
lock orb $0x40, 0x4(%rsi)
# CHECK: lock {{.*}} SMPLock
ret
.size _start, .-_start

.section .smp_locks,"a",@progbits
.long .L0 - .
.long .L1 - .

## Fake Linux Kernel sections.
.section __ksymtab,"a",@progbits
.section __ksymtab_gpl,"a",@progbits
29 changes: 23 additions & 6 deletions bolt/test/X86/linux-static-keys.s
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
## Check that BOLT correctly updates the Linux kernel static keys jump table.

# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-unknown %s -o %t.o
# RUN: link_fdata %s %t.o %t.fdata
# RUN: llvm-strip --strip-unneeded %t.o
# RUN: %clang %cflags -nostdlib %t.o -o %t.exe \
# RUN: -Wl,--image-base=0xffffffff80000000,--no-dynamic-linker,--no-eh-frame-hdr

Expand All @@ -11,6 +13,12 @@
# RUN: llvm-bolt %t.exe --print-normalized -o %t.out --keep-nops=0 \
# RUN: --bolt-info=0 |& FileCheck %s

## Verify that profile is matched correctly.

# RUN: llvm-bolt %t.exe --print-normalized -o %t.out --keep-nops=0 \
# RUN: --bolt-info=0 --data %t.fdata |& \
# RUN: FileCheck --check-prefix=CHECK-FDATA %s

## Verify the bindings again on the rewritten binary with nops removed.

# RUN: llvm-bolt %t.out -o %t.out.1 --print-normalized |& FileCheck %s
Expand All @@ -25,15 +33,24 @@ _start:
# CHECK: Binary Function "_start"
nop
.L0:
jmp .L1
jmp L1
# CHECK: jit
# CHECK-SAME: # ID: 1 {{.*}} # Likely: 0 # InitValue: 1
nop
.L1:
L1:
.nops 5
jmp .L0
# CHECK: jit
# CHECK-SAME: # ID: 2 {{.*}} # Likely: 1 # InitValue: 1
.L2:

## Check that a branch profile associated with a NOP is handled properly when
## dynamic branch is created.

# FDATA: 1 _start #L1# 1 _start #L2# 3 42
# CHECK-FDATA: jit {{.*}} # ID: 2
# CHECK-FDATA-NEXT: jmp
# CHECK-FDATA-NEXT: Successors: {{.*}} (mispreds: 3, count: 42)
L2:
nop
.size _start, .-_start

Expand All @@ -51,11 +68,11 @@ foo:
__start___jump_table:

.long .L0 - . # Jump address
.long .L1 - . # Target address
.long L1 - . # Target address
.quad 1 # Key address

.long .L1 - . # Jump address
.long .L2 - . # Target address
.long L1 - . # Jump address
.long L2 - . # Target address
.quad 0 # Key address

.globl __stop___jump_table
Expand Down
5 changes: 5 additions & 0 deletions bolt/test/X86/register-fragments-bolt-symbols.s
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
# PREAGG: B X:0 #chain.cold.0# 1 0
# RUN: perf2bolt %t.bolt -p %t.preagg --pa -o %t.bat.fdata -w %t.bat.yaml -v=1 \
# RUN: | FileCheck %s --check-prefix=CHECK-REGISTER
# RUN: FileCheck --input-file %t.bat.fdata --check-prefix=CHECK-FDATA %s
# RUN: FileCheck --input-file %t.bat.yaml --check-prefix=CHECK-YAML %s

# CHECK-SYMS: l df *ABS* [[#]] chain.s
# CHECK-SYMS: l F .bolt.org.text [[#]] chain
Expand All @@ -24,6 +26,9 @@

# CHECK-REGISTER: BOLT-INFO: marking chain.cold.0/1(*2) as a fragment of chain/2(*2)

# CHECK-FDATA: 0 [unknown] 0 1 chain/chain.s/2 10 0 1
# CHECK-YAML: - name: 'chain/chain.s/2'

.file "chain.s"
.text
.type chain, @function
Expand Down
10 changes: 7 additions & 3 deletions bolt/test/X86/sctc-bug4.test
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
# Check that fallthrough blocks are handled properly.
# Check that fallthrough blocks are handled properly and Offset annotation is
# set for conditional tail calls.

RUN: %clang %cflags %S/Inputs/sctc_bug4.s -o %t
RUN: llvm-bolt %t -o %t.null \
RUN: llvm-bolt %t -o %t.null --enable-bat \
RUN: -funcs=test_func -print-sctc -sequential-disassembly 2>&1 | FileCheck %s

CHECK: .Ltmp2 (3 instructions, align : 1)
CHECK-NEXT: CFI State : 0
CHECK-NEXT: Input offset: 0x24
CHECK-NEXT: Predecessors: .LFT1
CHECK-NEXT: 00000024: cmpq $0x20, %rsi
CHECK-NEXT: 00000028: ja dummy # TAILCALL {{.*}}# CTCTakenCount: 0
CHECK-NEXT: 00000028: ja dummy # TAILCALL # Offset: 53 # CTCTakenCount: 0
CHECK-NEXT: 0000002a: jmp .Ltmp4
CHECK-NEXT: Successors: .Ltmp4
CHECK-NEXT: CFI State: 0

CHECK: .Ltmp1 (2 instructions, align : 1)
CHECK-NEXT: CFI State : 0
CHECK-NEXT: Input offset: 0x2c
CHECK-NEXT: Predecessors: .LFT0
CHECK-NEXT: 0000002c: xorq %r11, %rax
CHECK-NEXT: 0000002f: retq
CHECK-NEXT: CFI State: 0

CHECK: .Ltmp4 (4 instructions, align : 1)
CHECK-NEXT: CFI State : 0
CHECK-NEXT: Input offset: 0x3a
CHECK-NEXT: Predecessors: .Ltmp2
40 changes: 40 additions & 0 deletions bolt/test/runtime/bolt-reserved.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// REQUIRES: system-linux

/*
* Check that llvm-bolt uses reserved space in a binary for allocating
* new sections.
*/

// RUN: %clang %s -o %t.exe -Wl,-q
// RUN: llvm-bolt %t.exe -o %t.bolt.exe 2>&1 | FileCheck %s
// RUN: %t.bolt.exe

// CHECK: BOLT-INFO: using reserved space

/*
* Check that llvm-bolt detects a condition when the reserved space is
* not enough for allocating new sections.
*/

// RUN: %clang %s -o %t.tiny.exe -Wl,--no-eh-frame-hdr -Wl,-q -DTINY
// RUN: not llvm-bolt %t.tiny.exe -o %t.tiny.bolt.exe 2>&1 | \
// RUN: FileCheck %s --check-prefix=CHECK-TINY

// CHECK-TINY: BOLT-ERROR: reserved space (1 byte) is smaller than required

#ifdef TINY
#define RSIZE "1"
#else
#define RSIZE "8192 * 1024"
#endif

asm(".pushsection .text \n\
.globl __bolt_reserved_start \n\
.type __bolt_reserved_start, @object \n\
__bolt_reserved_start: \n\
.space " RSIZE " \n\
.globl __bolt_reserved_end \n\
__bolt_reserved_end: \n\
.popsection");

int main() { return 0; }
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ const HeaderMapCollector::RegexHeaderMap *getSTLPostfixHeaderMap() {
static const HeaderMapCollector::RegexHeaderMap STLPostfixHeaderMap = {
{"include/__stdarg___gnuc_va_list.h$", "<cstdarg>"},
{"include/__stdarg___va_copy.h$", "<cstdarg>"},
{"include/__stdarg_header_macro.h$", "<cstdarg>"},
{"include/__stdarg_va_arg.h$", "<cstdarg>"},
{"include/__stdarg_va_copy.h$", "<cstdarg>"},
{"include/__stdarg_va_list.h$", "<cstdarg>"},
{"include/__stddef_header_macro.h$", "<cstddef>"},
{"include/__stddef_max_align_t.h$", "<cstddef>"},
{"include/__stddef_null.h$", "<cstddef>"},
{"include/__stddef_nullptr_t.h$", "<cstddef>"},
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
10 changes: 5 additions & 5 deletions clang-tools-extra/clang-tidy/ClangTidy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -373,11 +373,11 @@ static CheckersList getAnalyzerCheckersAndPackages(ClangTidyContext &Context,

const auto &RegisteredCheckers =
AnalyzerOptions::getRegisteredCheckers(IncludeExperimental);
bool AnalyzerChecksEnabled = false;
for (StringRef CheckName : RegisteredCheckers) {
std::string ClangTidyCheckName((AnalyzerCheckNamePrefix + CheckName).str());
AnalyzerChecksEnabled |= Context.isCheckEnabled(ClangTidyCheckName);
}
const bool AnalyzerChecksEnabled =
llvm::any_of(RegisteredCheckers, [&](StringRef CheckName) -> bool {
return Context.isCheckEnabled(
(AnalyzerCheckNamePrefix + CheckName).str());
});

if (!AnalyzerChecksEnabled)
return List;
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@
//===----------------------------------------------------------------------===//

#include "CastingThroughVoidCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Expr.h"
#include "clang/AST/Type.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "llvm/ADT/StringSet.h"

using namespace clang::ast_matchers;

Expand All @@ -27,7 +25,8 @@ void CastingThroughVoidCheck::registerMatchers(MatchFinder *Finder) {
hasSourceExpression(
explicitCastExpr(
hasSourceExpression(
expr(hasType(qualType().bind("source_type")))),
expr(hasType(qualType(unless(pointsTo(voidType())))
.bind("source_type")))),
hasDestinationType(
qualType(pointsTo(voidType())).bind("void_type")))
.bind("cast"))),
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 @@ -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 @@ -178,8 +178,11 @@ std::optional<RenamerClangTidyCheck::FailureInfo>
ReservedIdentifierCheck::getDeclFailureInfo(const NamedDecl *Decl,
const SourceManager &) const {
assert(Decl && Decl->getIdentifier() && !Decl->getName().empty() &&
!Decl->isImplicit() &&
"Decl must be an explicit identifier with a name.");
// Implicit identifiers cannot fail.
if (Decl->isImplicit())
return std::nullopt;

return getFailureInfoImpl(
Decl->getName(), isa<TranslationUnitDecl>(Decl->getDeclContext()),
/*IsMacro = */ false, getLangOpts(), Invert, AllowedIdentifiers);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,25 @@ namespace clang::tidy::bugprone {

void ReturnConstRefFromParameterCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(
returnStmt(hasReturnValue(declRefExpr(to(parmVarDecl(hasType(
hasCanonicalType(matchers::isReferenceToConst())))))))
returnStmt(
hasReturnValue(declRefExpr(to(parmVarDecl(hasType(hasCanonicalType(
qualType(matchers::isReferenceToConst()).bind("type"))))))),
hasAncestor(functionDecl(hasReturnTypeLoc(
loc(qualType(hasCanonicalType(equalsBoundNode("type"))))))))
.bind("ret"),
this);
}

void ReturnConstRefFromParameterCheck::check(
const MatchFinder::MatchResult &Result) {
const auto *R = Result.Nodes.getNodeAs<ReturnStmt>("ret");
diag(R->getRetValue()->getBeginLoc(),
"returning a constant reference parameter may cause a use-after-free "
"when the parameter is constructed from a temporary");
const SourceRange Range = R->getRetValue()->getSourceRange();
if (Range.isInvalid())
return;
diag(Range.getBegin(),
"returning a constant reference parameter may cause use-after-free "
"when the parameter is constructed from a temporary")
<< Range;
}

} // namespace clang::tidy::bugprone
4 changes: 4 additions & 0 deletions clang-tools-extra/clang-tidy/cert/CERTTidyModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "../misc/StaticAssertCheck.h"
#include "../misc/ThrowByValueCatchByReferenceCheck.h"
#include "../performance/MoveConstructorInitCheck.h"
#include "../readability/EnumInitialValueCheck.h"
#include "../readability/UppercaseLiteralSuffixCheck.h"
#include "CommandProcessorCheck.h"
#include "DefaultOperatorNewAlignmentCheck.h"
Expand Down Expand Up @@ -299,6 +300,9 @@ class CERTModule : public ClangTidyModule {
"cert-flp37-c");
// FIO
CheckFactories.registerCheck<misc::NonCopyableObjectsCheck>("cert-fio38-c");
// INT
CheckFactories.registerCheck<readability::EnumInitialValueCheck>(
"cert-int09-c");
// MSC
CheckFactories.registerCheck<bugprone::UnsafeFunctionsCheck>(
"cert-msc24-c");
Expand Down
5 changes: 3 additions & 2 deletions clang-tools-extra/clang-tidy/hicpp/SignedBitwiseCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "SignedBitwiseCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"

using namespace clang::ast_matchers;
using namespace clang::ast_matchers::internal;
Expand All @@ -29,8 +30,8 @@ void SignedBitwiseCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
void SignedBitwiseCheck::registerMatchers(MatchFinder *Finder) {
const auto SignedIntegerOperand =
(IgnorePositiveIntegerLiterals
? expr(ignoringImpCasts(hasType(isSignedInteger())),
unless(integerLiteral()))
? expr(ignoringImpCasts(
allOf(hasType(isSignedInteger()), unless(integerLiteral()))))
: expr(ignoringImpCasts(hasType(isSignedInteger()))))
.bind("signed-operand");

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 @@ -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
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
8 changes: 6 additions & 2 deletions clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,16 +129,20 @@ 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
: ReplacementPrintFunction;
if (!Converter.canApply()) {
diag(PrintfCall->getBeginLoc(),
"unable to use '%0' instead of %1 because %2")
<< ReplacementFunction << OldFunction->getIdentifier()
<< PrintfCall->getSourceRange() << ReplacementFunction
<< OldFunction->getIdentifier()
<< Converter.conversionNotPossibleReason();
return;
}
Expand Down
21 changes: 4 additions & 17 deletions clang-tools-extra/clang-tidy/readability/ConstReturnTypeCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,6 @@ AST_MATCHER(QualType, isLocalConstQualified) {
return Node.isLocalConstQualified();
}

AST_MATCHER(QualType, isTypeOfType) {
return isa<TypeOfType>(Node.getTypePtr());
}

AST_MATCHER(QualType, isTypeOfExprType) {
return isa<TypeOfExprType>(Node.getTypePtr());
}

struct CheckResult {
// Source range of the relevant `const` token in the definition being checked.
CharSourceRange ConstRange;
Expand Down Expand Up @@ -110,16 +102,11 @@ void ConstReturnTypeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
void ConstReturnTypeCheck::registerMatchers(MatchFinder *Finder) {
// Find all function definitions for which the return types are `const`
// qualified, ignoring decltype types.
auto NonLocalConstType =
qualType(unless(isLocalConstQualified()),
anyOf(decltypeType(), autoType(), isTypeOfType(),
isTypeOfExprType(), substTemplateTypeParmType()));
Finder->addMatcher(
functionDecl(
returns(allOf(isConstQualified(), unless(NonLocalConstType))),
anyOf(isDefinition(), cxxMethodDecl(isPure())),
// Overridden functions are not actionable.
unless(cxxMethodDecl(isOverride())))
functionDecl(returns(isLocalConstQualified()),
anyOf(isDefinition(), cxxMethodDecl(isPure())),
// Overridden functions are not actionable.
unless(cxxMethodDecl(isOverride())))
.bind("func"),
this);
}
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 All @@ -1374,6 +1374,10 @@ IdentifierNamingCheck::getFailureInfo(
std::optional<RenamerClangTidyCheck::FailureInfo>
IdentifierNamingCheck::getDeclFailureInfo(const NamedDecl *Decl,
const SourceManager &SM) const {
// Implicit identifiers cannot be renamed.
if (Decl->isImplicit())
return std::nullopt;

SourceLocation Loc = Decl->getLocation();
const FileStyle &FileStyle = getStyleForFile(SM.getFilename(Loc));
if (!FileStyle.isActive())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//

#include "SimplifyBooleanExprCheck.h"
#include "clang/AST/Expr.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Lex/Lexer.h"
#include "llvm/Support/SaveAndRestore.h"
Expand Down Expand Up @@ -280,9 +281,8 @@ class SimplifyBooleanExprCheck::Visitor : public RecursiveASTVisitor<Visitor> {
if (!S) {
return true;
}
if (Check->IgnoreMacros && S->getBeginLoc().isMacroID()) {
if (Check->canBeBypassed(S))
return false;
}
if (!shouldIgnore(S))
StmtStack.push_back(S);
return true;
Expand Down Expand Up @@ -513,17 +513,23 @@ class SimplifyBooleanExprCheck::Visitor : public RecursiveASTVisitor<Visitor> {
return true;
}

static bool isUnaryLNot(const Expr *E) {
return isa<UnaryOperator>(E) &&
bool isExpectedUnaryLNot(const Expr *E) {
return !Check->canBeBypassed(E) && isa<UnaryOperator>(E) &&
cast<UnaryOperator>(E)->getOpcode() == UO_LNot;
}

bool isExpectedBinaryOp(const Expr *E) {
const auto *BinaryOp = dyn_cast<BinaryOperator>(E);
return !Check->canBeBypassed(E) && BinaryOp && BinaryOp->isLogicalOp() &&
BinaryOp->getType()->isBooleanType();
}

template <typename Functor>
static bool checkEitherSide(const BinaryOperator *BO, Functor Func) {
return Func(BO->getLHS()) || Func(BO->getRHS());
}

static bool nestedDemorgan(const Expr *E, unsigned NestingLevel) {
bool nestedDemorgan(const Expr *E, unsigned NestingLevel) {
const auto *BO = dyn_cast<BinaryOperator>(E->IgnoreUnlessSpelledInSource());
if (!BO)
return false;
Expand All @@ -539,15 +545,13 @@ class SimplifyBooleanExprCheck::Visitor : public RecursiveASTVisitor<Visitor> {
return true;
case BO_LAnd:
case BO_LOr:
if (checkEitherSide(BO, isUnaryLNot))
return true;
if (NestingLevel) {
if (checkEitherSide(BO, [NestingLevel](const Expr *E) {
return nestedDemorgan(E, NestingLevel - 1);
}))
return true;
}
return false;
return checkEitherSide(
BO,
[this](const Expr *E) { return isExpectedUnaryLNot(E); }) ||
(NestingLevel &&
checkEitherSide(BO, [this, NestingLevel](const Expr *E) {
return nestedDemorgan(E, NestingLevel - 1);
}));
default:
return false;
}
Expand All @@ -556,19 +560,19 @@ class SimplifyBooleanExprCheck::Visitor : public RecursiveASTVisitor<Visitor> {
bool TraverseUnaryOperator(UnaryOperator *Op) {
if (!Check->SimplifyDeMorgan || Op->getOpcode() != UO_LNot)
return Base::TraverseUnaryOperator(Op);
Expr *SubImp = Op->getSubExpr()->IgnoreImplicit();
auto *Parens = dyn_cast<ParenExpr>(SubImp);
auto *BinaryOp =
Parens
? dyn_cast<BinaryOperator>(Parens->getSubExpr()->IgnoreImplicit())
: dyn_cast<BinaryOperator>(SubImp);
if (!BinaryOp || !BinaryOp->isLogicalOp() ||
!BinaryOp->getType()->isBooleanType())
const Expr *SubImp = Op->getSubExpr()->IgnoreImplicit();
const auto *Parens = dyn_cast<ParenExpr>(SubImp);
const Expr *SubExpr =
Parens ? Parens->getSubExpr()->IgnoreImplicit() : SubImp;
if (!isExpectedBinaryOp(SubExpr))
return Base::TraverseUnaryOperator(Op);
const auto *BinaryOp = cast<BinaryOperator>(SubExpr);
if (Check->SimplifyDeMorganRelaxed ||
checkEitherSide(BinaryOp, isUnaryLNot) ||
checkEitherSide(BinaryOp,
[](const Expr *E) { return nestedDemorgan(E, 1); })) {
checkEitherSide(
BinaryOp,
[this](const Expr *E) { return isExpectedUnaryLNot(E); }) ||
checkEitherSide(
BinaryOp, [this](const Expr *E) { return nestedDemorgan(E, 1); })) {
if (Check->reportDeMorgan(Context, Op, BinaryOp, !IsProcessing, parent(),
Parens) &&
!Check->areDiagsSelfContained()) {
Expand Down Expand Up @@ -694,6 +698,10 @@ void SimplifyBooleanExprCheck::check(const MatchFinder::MatchResult &Result) {
Visitor(this, *Result.Context).traverse();
}

bool SimplifyBooleanExprCheck::canBeBypassed(const Stmt *S) const {
return IgnoreMacros && S->getBeginLoc().isMacroID();
}

void SimplifyBooleanExprCheck::issueDiag(const ASTContext &Context,
SourceLocation Loc,
StringRef Description,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ class SimplifyBooleanExprCheck : public ClangTidyCheck {
StringRef Description, SourceRange ReplacementRange,
StringRef Replacement);

bool canBeBypassed(const Stmt *S) const;

const bool IgnoreMacros;
const bool ChainedConditionalReturn;
const bool ChainedConditionalAssignment;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,6 @@ void StaticAccessedThroughInstanceCheck::check(

const Expr *BaseExpr = MemberExpression->getBase();

// Do not warn for overloaded -> operators.
if (isa<CXXOperatorCallExpr>(BaseExpr))
return;

const QualType BaseType =
BaseExpr->getType()->isPointerType()
? BaseExpr->getType()->getPointeeType().getUnqualifiedType()
Expand All @@ -89,17 +85,30 @@ void StaticAccessedThroughInstanceCheck::check(
return;

SourceLocation MemberExprStartLoc = MemberExpression->getBeginLoc();
auto Diag =
diag(MemberExprStartLoc, "static member accessed through instance");

if (BaseExpr->HasSideEffects(*AstContext) ||
getNameSpecifierNestingLevel(BaseType) > NameSpecifierNestingThreshold)
return;
auto CreateFix = [&] {
return FixItHint::CreateReplacement(
CharSourceRange::getCharRange(MemberExprStartLoc,
MemberExpression->getMemberLoc()),
BaseTypeName + "::");
};

{
auto Diag =
diag(MemberExprStartLoc, "static member accessed through instance");

if (getNameSpecifierNestingLevel(BaseType) > NameSpecifierNestingThreshold)
return;

if (!BaseExpr->HasSideEffects(*AstContext,
/* IncludePossibleEffects =*/true)) {
Diag << CreateFix();
return;
}
}

Diag << FixItHint::CreateReplacement(
CharSourceRange::getCharRange(MemberExprStartLoc,
MemberExpression->getMemberLoc()),
BaseTypeName + "::");
diag(MemberExprStartLoc, "member base expression may carry some side effects",
DiagnosticIDs::Level::Note)
<< BaseExpr->getSourceRange() << CreateFix();
}

} // namespace clang::tidy::readability
27 changes: 23 additions & 4 deletions clang-tools-extra/clang-tidy/readability/StringCompareCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,43 @@
//===----------------------------------------------------------------------===//

#include "StringCompareCheck.h"
#include "../utils/FixItHintUtils.h"
#include "../utils/OptionsUtils.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Tooling/FixIt.h"
#include "llvm/ADT/StringRef.h"

using namespace clang::ast_matchers;
namespace optutils = clang::tidy::utils::options;

namespace clang::tidy::readability {

static const StringRef CompareMessage = "do not use 'compare' to test equality "
"of strings; use the string equality "
"operator instead";

static const StringRef DefaultStringLikeClasses = "::std::basic_string;"
"::std::basic_string_view";

StringCompareCheck::StringCompareCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
StringLikeClasses(optutils::parseStringList(
Options.get("StringLikeClasses", DefaultStringLikeClasses))) {}

void StringCompareCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "StringLikeClasses",
optutils::serializeStringList(StringLikeClasses));
}

void StringCompareCheck::registerMatchers(MatchFinder *Finder) {
if (StringLikeClasses.empty()) {
return;
}
const auto StrCompare = cxxMemberCallExpr(
callee(cxxMethodDecl(hasName("compare"),
ofClass(classTemplateSpecializationDecl(
hasName("::std::basic_string"))))),
callee(cxxMethodDecl(hasName("compare"), ofClass(cxxRecordDecl(hasAnyName(
StringLikeClasses))))),
hasArgument(0, expr().bind("str2")), argumentCountIs(1),
callee(memberExpr().bind("str1")));

Expand Down
10 changes: 8 additions & 2 deletions clang-tools-extra/clang-tidy/readability/StringCompareCheck.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_STRINGCOMPARECHECK_H

#include "../ClangTidyCheck.h"
#include <vector>

namespace clang::tidy::readability {

Expand All @@ -20,13 +21,18 @@ namespace clang::tidy::readability {
/// http://clang.llvm.org/extra/clang-tidy/checks/readability/string-compare.html
class StringCompareCheck : public ClangTidyCheck {
public:
StringCompareCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context) {}
StringCompareCheck(StringRef Name, ClangTidyContext *Context);

bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
return LangOpts.CPlusPlus;
}

void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;

private:
const std::vector<StringRef> StringLikeClasses;
};

} // namespace clang::tidy::readability
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
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
198 changes: 106 additions & 92 deletions clang-tools-extra/clang-tidy/utils/RenamerClangTidyCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ struct DenseMapInfo<clang::tidy::RenamerClangTidyCheck::NamingCheckId> {
namespace clang::tidy {

namespace {

class NameLookup {
llvm::PointerIntPair<const NamedDecl *, 1, bool> Data;

Expand All @@ -78,19 +79,58 @@ class NameLookup {
operator bool() const { return !hasMultipleResolutions(); }
const NamedDecl *operator*() const { return getDecl(); }
};

} // namespace

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;
}
}
return nullptr;
}

/// Returns the function that \p Method is overridding. If There are none or
/// multiple overrides it returns nullptr. If the overridden function itself is
/// overridding then it will recurse up to find the first decl of the function.
static const CXXMethodDecl *getOverrideMethod(const CXXMethodDecl *Method) {
if (Method->size_overridden_methods() != 1)
return nullptr;

while (true) {
Method = *Method->begin_overridden_methods();
assert(Method && "Overridden method shouldn't be null");
unsigned NumOverrides = Method->size_overridden_methods();
if (NumOverrides == 0)
return Method;
if (NumOverrides > 1)
return nullptr;
}
}

static bool hasNoName(const NamedDecl *Decl) {
return !Decl->getIdentifier() || Decl->getName().empty();
}

static const NamedDecl *getFailureForNamedDecl(const NamedDecl *ND) {
const auto *Canonical = cast<NamedDecl>(ND->getCanonicalDecl());
if (Canonical != ND)
return Canonical;

if (const auto *Method = dyn_cast<CXXMethodDecl>(ND)) {
if (const CXXMethodDecl *Overridden = getOverrideMethod(Method))
Canonical = cast<NamedDecl>(Overridden->getCanonicalDecl());

if (Canonical != ND)
return Canonical;
}

return ND;
}

/// Returns a decl matching the \p DeclName in \p Parent or one of its base
/// classes. If \p AggressiveTemplateLookup is `true` then it will check
/// template dependent base classes as well.
Expand Down Expand Up @@ -132,24 +172,6 @@ static NameLookup findDeclInBases(const CXXRecordDecl &Parent,
return NameLookup(Found); // If nullptr, decl wasn't found.
}

/// Returns the function that \p Method is overridding. If There are none or
/// multiple overrides it returns nullptr. If the overridden function itself is
/// overridding then it will recurse up to find the first decl of the function.
static const CXXMethodDecl *getOverrideMethod(const CXXMethodDecl *Method) {
if (Method->size_overridden_methods() != 1)
return nullptr;

while (true) {
Method = *Method->begin_overridden_methods();
assert(Method && "Overridden method shouldn't be null");
unsigned NumOverrides = Method->size_overridden_methods();
if (NumOverrides == 0)
return Method;
if (NumOverrides > 1)
return nullptr;
}
}

namespace {

/// Callback supplies macros to RenamerClangTidyCheck::checkMacro
Expand Down Expand Up @@ -192,10 +214,6 @@ class RenamerClangTidyVisitor
: Check(Check), SM(SM),
AggressiveDependentMemberLookup(AggressiveDependentMemberLookup) {}

static bool hasNoName(const NamedDecl *Decl) {
return !Decl->getIdentifier() || Decl->getName().empty();
}

bool shouldVisitTemplateInstantiations() const { return true; }

bool shouldVisitImplicitCode() const { return false; }
Expand Down Expand Up @@ -246,29 +264,10 @@ class RenamerClangTidyVisitor
}

bool VisitNamedDecl(NamedDecl *Decl) {
if (hasNoName(Decl))
return true;

const auto *Canonical = cast<NamedDecl>(Decl->getCanonicalDecl());
if (Canonical != Decl) {
Check->addUsage(Canonical, Decl->getLocation(), SM);
return true;
}

// Fix overridden methods
if (const auto *Method = dyn_cast<CXXMethodDecl>(Decl)) {
if (const CXXMethodDecl *Overridden = getOverrideMethod(Method)) {
Check->addUsage(Overridden, Method->getLocation(), SM);
return true; // Don't try to add the actual decl as a Failure.
}
}

// Ignore ClassTemplateSpecializationDecl which are creating duplicate
// replacements with CXXRecordDecl.
if (isa<ClassTemplateSpecializationDecl>(Decl))
return true;

Check->checkNamedDecl(Decl, SM);
SourceRange UsageRange =
DeclarationNameInfo(Decl->getDeclName(), Decl->getLocation())
.getSourceRange();
Check->addUsage(Decl, UsageRange, SM);
return true;
}

Expand Down Expand Up @@ -413,82 +412,97 @@ void RenamerClangTidyCheck::registerPPCallbacks(
std::make_unique<RenamerClangTidyCheckPPCallbacks>(SM, this));
}

void RenamerClangTidyCheck::addUsage(
const RenamerClangTidyCheck::NamingCheckId &Decl, SourceRange Range,
const SourceManager &SourceMgr) {
std::pair<RenamerClangTidyCheck::NamingCheckFailureMap::iterator, bool>
RenamerClangTidyCheck::addUsage(
const RenamerClangTidyCheck::NamingCheckId &FailureId,
SourceRange UsageRange, const SourceManager &SourceMgr) {
// Do nothing if the provided range is invalid.
if (Range.isInvalid())
return;
if (UsageRange.isInvalid())
return {NamingCheckFailures.end(), false};

// If we have a source manager, use it to convert to the spelling location for
// performing the fix. This is necessary because macros can map the same
// spelling location to different source locations, and we only want to fix
// the token once, before it is expanded by the macro.
SourceLocation FixLocation = Range.getBegin();
// Get the spelling location for performing the fix. This is necessary because
// macros can map the same spelling location to different source locations,
// and we only want to fix the token once, before it is expanded by the macro.
SourceLocation FixLocation = UsageRange.getBegin();
FixLocation = SourceMgr.getSpellingLoc(FixLocation);
if (FixLocation.isInvalid())
return;
return {NamingCheckFailures.end(), false};

auto EmplaceResult = NamingCheckFailures.try_emplace(FailureId);
NamingCheckFailure &Failure = EmplaceResult.first->second;

// Try to insert the identifier location in the Usages map, and bail out if it
// is already in there
RenamerClangTidyCheck::NamingCheckFailure &Failure =
NamingCheckFailures[Decl];
if (!Failure.RawUsageLocs.insert(FixLocation).second)
return;
return EmplaceResult;

if (!Failure.shouldFix())
return;
if (Failure.FixStatus != RenamerClangTidyCheck::ShouldFixStatus::ShouldFix)
return EmplaceResult;

if (SourceMgr.isWrittenInScratchSpace(FixLocation))
Failure.FixStatus = RenamerClangTidyCheck::ShouldFixStatus::InsideMacro;

if (!utils::rangeCanBeFixed(Range, &SourceMgr))
if (!utils::rangeCanBeFixed(UsageRange, &SourceMgr))
Failure.FixStatus = RenamerClangTidyCheck::ShouldFixStatus::InsideMacro;

return EmplaceResult;
}

void RenamerClangTidyCheck::addUsage(const NamedDecl *Decl, SourceRange Range,
void RenamerClangTidyCheck::addUsage(const NamedDecl *Decl,
SourceRange UsageRange,
const SourceManager &SourceMgr) {
// Don't keep track for non-identifier names.
auto *II = Decl->getIdentifier();
if (!II)
if (hasNoName(Decl))
return;

// Ignore ClassTemplateSpecializationDecl which are creating duplicate
// replacements with CXXRecordDecl.
if (isa<ClassTemplateSpecializationDecl>(Decl))
return;
if (const auto *Method = dyn_cast<CXXMethodDecl>(Decl)) {
if (const CXXMethodDecl *Overridden = getOverrideMethod(Method))
Decl = Overridden;
}
Decl = cast<NamedDecl>(Decl->getCanonicalDecl());
return addUsage(
RenamerClangTidyCheck::NamingCheckId(Decl->getLocation(), II->getName()),
Range, SourceMgr);
}

void RenamerClangTidyCheck::checkNamedDecl(const NamedDecl *Decl,
const SourceManager &SourceMgr) {
std::optional<FailureInfo> MaybeFailure = getDeclFailureInfo(Decl, SourceMgr);
// We don't want to create a failure for every NamedDecl we find. Ideally
// there is just one NamedDecl in every group of "related" NamedDecls that
// becomes the failure. This NamedDecl and all of its related NamedDecls
// become usages. E.g. Since NamedDecls are Redeclarable, only the canonical
// NamedDecl becomes the failure and all redeclarations become usages.
const NamedDecl *FailureDecl = getFailureForNamedDecl(Decl);

std::optional<FailureInfo> MaybeFailure =
getDeclFailureInfo(FailureDecl, SourceMgr);
if (!MaybeFailure)
return;

FailureInfo &Info = *MaybeFailure;
NamingCheckFailure &Failure =
NamingCheckFailures[NamingCheckId(Decl->getLocation(), Decl->getName())];
SourceRange Range =
DeclarationNameInfo(Decl->getDeclName(), Decl->getLocation())
.getSourceRange();

const IdentifierTable &Idents = Decl->getASTContext().Idents;
auto CheckNewIdentifier = Idents.find(Info.Fixup);
NamingCheckId FailureId(FailureDecl->getLocation(), FailureDecl->getName());

auto [FailureIter, NewFailure] = addUsage(FailureId, UsageRange, SourceMgr);

if (FailureIter == NamingCheckFailures.end()) {
// Nothing to do if the usage wasn't accepted.
return;
}
if (!NewFailure) {
// FailureInfo has already been provided.
return;
}

// Update the stored failure with info regarding the FailureDecl.
NamingCheckFailure &Failure = FailureIter->second;
Failure.Info = std::move(*MaybeFailure);

// Don't overwritte the failure status if it was already set.
if (!Failure.shouldFix()) {
return;
}
const IdentifierTable &Idents = FailureDecl->getASTContext().Idents;
auto CheckNewIdentifier = Idents.find(Failure.Info.Fixup);
if (CheckNewIdentifier != Idents.end()) {
const IdentifierInfo *Ident = CheckNewIdentifier->second;
if (Ident->isKeyword(getLangOpts()))
Failure.FixStatus = ShouldFixStatus::ConflictsWithKeyword;
else if (Ident->hasMacroDefinition())
Failure.FixStatus = ShouldFixStatus::ConflictsWithMacroDefinition;
} else if (!isValidAsciiIdentifier(Info.Fixup)) {
} else if (!isValidAsciiIdentifier(Failure.Info.Fixup)) {
Failure.FixStatus = ShouldFixStatus::FixInvalidIdentifier;
}

Failure.Info = std::move(Info);
addUsage(Decl, Range, SourceMgr);
}

void RenamerClangTidyCheck::check(const MatchFinder::MatchResult &Result) {
Expand Down
14 changes: 8 additions & 6 deletions clang-tools-extra/clang-tidy/utils/RenamerClangTidyCheck.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,9 @@ class RenamerClangTidyCheck : public ClangTidyCheck {
void expandMacro(const Token &MacroNameTok, const MacroInfo *MI,
const SourceManager &SourceMgr);

void addUsage(const RenamerClangTidyCheck::NamingCheckId &Decl,
SourceRange Range, const SourceManager &SourceMgr);

/// Convenience method when the usage to be added is a NamedDecl.
void addUsage(const NamedDecl *Decl, SourceRange Range,
const SourceManager &SourceMgr);

void checkNamedDecl(const NamedDecl *Decl, const SourceManager &SourceMgr);

protected:
/// Overridden by derived classes, returns information about if and how a Decl
/// failed the check. A 'std::nullopt' result means the Decl did not fail the
Expand Down Expand Up @@ -158,6 +152,14 @@ class RenamerClangTidyCheck : public ClangTidyCheck {
const NamingCheckFailure &Failure) const = 0;

private:
// Manage additions to the Failure/usage map
//
// return the result of NamingCheckFailures::try_emplace() if the usage was
// accepted.
std::pair<NamingCheckFailureMap::iterator, bool>
addUsage(const RenamerClangTidyCheck::NamingCheckId &FailureId,
SourceRange UsageRange, const SourceManager &SourceMgr);

NamingCheckFailureMap NamingCheckFailures;
const bool AggressiveDependentMemberLookup;
};
Expand Down
4 changes: 3 additions & 1 deletion clang-tools-extra/clangd/Preamble.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -918,7 +918,9 @@ void PreamblePatch::apply(CompilerInvocation &CI) const {
// no guarantees around using arbitrary options when reusing PCHs, and
// different target opts can result in crashes, see
// ParsedASTTest.PreambleWithDifferentTarget.
CI.TargetOpts = Baseline->TargetOpts;
// Make sure this is a deep copy, as the same Baseline might be used
// concurrently.
*CI.TargetOpts = *Baseline->TargetOpts;

// No need to map an empty file.
if (PatchContents.empty())
Expand Down
2 changes: 2 additions & 0 deletions clang-tools-extra/clangd/index/CanonicalIncludes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ namespace {
const std::pair<llvm::StringRef, llvm::StringRef> IncludeMappings[] = {
{"include/__stdarg___gnuc_va_list.h", "<cstdarg>"},
{"include/__stdarg___va_copy.h", "<cstdarg>"},
{"include/__stdarg_header_macro.h", "<cstdarg>"},
{"include/__stdarg_va_arg.h", "<cstdarg>"},
{"include/__stdarg_va_copy.h", "<cstdarg>"},
{"include/__stdarg_va_list.h", "<cstdarg>"},
{"include/__stddef_header_macro.h", "<cstddef>"},
{"include/__stddef_max_align_t.h", "<cstddef>"},
{"include/__stddef_null.h", "<cstddef>"},
{"include/__stddef_nullptr_t.h", "<cstddef>"},
Expand Down
99 changes: 55 additions & 44 deletions clang-tools-extra/clangd/refactor/tweaks/ScopifyEnum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,12 @@ namespace {
/// void f() { E e1 = EV1; }
///
/// After:
/// enum class E { EV1, EV2 };
/// void f() { E e1 = E::EV1; }
/// enum class E { V1, V2 };
/// void f() { E e1 = E::V1; }
///
/// Note that the respective project code might not compile anymore
/// if it made use of the now-gone implicit conversion to int.
/// This is out of scope for this tweak.
///
/// TODO: In the above example, we could detect that the values
/// start with the enum name, and remove that prefix.

class ScopifyEnum : public Tweak {
const char *id() const final;
Expand All @@ -63,14 +60,13 @@ class ScopifyEnum : public Tweak {
std::function<tooling::Replacement(StringRef, StringRef, unsigned)>;
llvm::Error addClassKeywordToDeclarations();
llvm::Error scopifyEnumValues();
llvm::Error scopifyEnumValue(const EnumConstantDecl &CD, StringRef Prefix);
llvm::Error scopifyEnumValue(const EnumConstantDecl &CD, StringRef EnumName,
bool StripPrefix);
llvm::Expected<StringRef> getContentForFile(StringRef FilePath);
unsigned getOffsetFromPosition(const Position &Pos, StringRef Content) const;
llvm::Error addReplacementForReference(const ReferencesResult::Reference &Ref,
const MakeReplacement &GetReplacement);
llvm::Error addReplacement(StringRef FilePath, StringRef Content,
const tooling::Replacement &Replacement);
Position getPosition(const Decl &D) const;

const EnumDecl *D = nullptr;
const Selection *S = nullptr;
Expand Down Expand Up @@ -109,7 +105,8 @@ Expected<Tweak::Effect> ScopifyEnum::apply(const Selection &Inputs) {

llvm::Error ScopifyEnum::addClassKeywordToDeclarations() {
for (const auto &Ref :
findReferences(*S->AST, getPosition(*D), 0, S->Index, false)
findReferences(*S->AST, sourceLocToPosition(*SM, D->getBeginLoc()), 0,
S->Index, false)
.References) {
if (!(Ref.Attributes & ReferencesResult::Declaration))
continue;
Expand All @@ -125,25 +122,46 @@ llvm::Error ScopifyEnum::addClassKeywordToDeclarations() {
}

llvm::Error ScopifyEnum::scopifyEnumValues() {
std::string PrefixToInsert(D->getName());
PrefixToInsert += "::";
for (auto E : D->enumerators()) {
if (auto Err = scopifyEnumValue(*E, PrefixToInsert))
StringRef EnumName(D->getName());
bool StripPrefix = true;
for (const EnumConstantDecl *E : D->enumerators()) {
if (!E->getName().starts_with(EnumName)) {
StripPrefix = false;
break;
}
}
for (const EnumConstantDecl *E : D->enumerators()) {
if (auto Err = scopifyEnumValue(*E, EnumName, StripPrefix))
return Err;
}
return llvm::Error::success();
}

llvm::Error ScopifyEnum::scopifyEnumValue(const EnumConstantDecl &CD,
StringRef Prefix) {
StringRef EnumName,
bool StripPrefix) {
for (const auto &Ref :
findReferences(*S->AST, getPosition(CD), 0, S->Index, false)
findReferences(*S->AST, sourceLocToPosition(*SM, CD.getBeginLoc()), 0,
S->Index, false)
.References) {
if (Ref.Attributes & ReferencesResult::Declaration)
if (Ref.Attributes & ReferencesResult::Declaration) {
if (StripPrefix) {
const auto MakeReplacement = [&EnumName](StringRef FilePath,
StringRef Content,
unsigned Offset) {
unsigned Length = EnumName.size();
if (Content[Offset + Length] == '_')
++Length;
return tooling::Replacement(FilePath, Offset, Length, {});
};
if (auto Err = addReplacementForReference(Ref, MakeReplacement))
return Err;
}
continue;
}

const auto MakeReplacement = [&Prefix](StringRef FilePath,
StringRef Content, unsigned Offset) {
const auto MakeReplacement = [&](StringRef FilePath, StringRef Content,
unsigned Offset) {
const auto IsAlreadyScoped = [Content, Offset] {
if (Offset < 2)
return false;
Expand All @@ -164,9 +182,18 @@ llvm::Error ScopifyEnum::scopifyEnumValue(const EnumConstantDecl &CD,
}
return false;
};
return IsAlreadyScoped()
? tooling::Replacement()
: tooling::Replacement(FilePath, Offset, 0, Prefix);
if (StripPrefix) {
const int ExtraLength =
Content[Offset + EnumName.size()] == '_' ? 1 : 0;
if (IsAlreadyScoped())
return tooling::Replacement(FilePath, Offset,
EnumName.size() + ExtraLength, {});
return tooling::Replacement(FilePath, Offset + EnumName.size(),
ExtraLength, "::");
}
return IsAlreadyScoped() ? tooling::Replacement()
: tooling::Replacement(FilePath, Offset, 0,
EnumName.str() + "::");
};
if (auto Err = addReplacementForReference(Ref, MakeReplacement))
return Err;
Expand All @@ -187,27 +214,19 @@ llvm::Expected<StringRef> ScopifyEnum::getContentForFile(StringRef FilePath) {
return Content;
}

unsigned int ScopifyEnum::getOffsetFromPosition(const Position &Pos,
StringRef Content) const {
unsigned int Offset = 0;

for (std::size_t LinesRemaining = Pos.line;
Offset < Content.size() && LinesRemaining;) {
if (Content[Offset++] == '\n')
--LinesRemaining;
}
return Offset + Pos.character;
}

llvm::Error
ScopifyEnum::addReplacementForReference(const ReferencesResult::Reference &Ref,
const MakeReplacement &GetReplacement) {
StringRef FilePath = Ref.Loc.uri.file();
auto Content = getContentForFile(FilePath);
llvm::Expected<StringRef> Content = getContentForFile(FilePath);
if (!Content)
return Content.takeError();
unsigned Offset = getOffsetFromPosition(Ref.Loc.range.start, *Content);
tooling::Replacement Replacement = GetReplacement(FilePath, *Content, Offset);
llvm::Expected<size_t> Offset =
positionToOffset(*Content, Ref.Loc.range.start);
if (!Offset)
return Offset.takeError();
tooling::Replacement Replacement =
GetReplacement(FilePath, *Content, *Offset);
if (Replacement.isApplicable())
return addReplacement(FilePath, *Content, Replacement);
return llvm::Error::success();
Expand All @@ -223,13 +242,5 @@ ScopifyEnum::addReplacement(StringRef FilePath, StringRef Content,
return llvm::Error::success();
}

Position ScopifyEnum::getPosition(const Decl &D) const {
const SourceLocation Loc = D.getLocation();
Position Pos;
Pos.line = SM->getSpellingLineNumber(Loc) - 1;
Pos.character = SM->getSpellingColumnNumber(Loc) - 1;
return Pos;
}

} // namespace
} // namespace clang::clangd
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# RUN: clangd -input-style=delimited -sync -input-mirror-file %t < %s
# RUN: grep '{"jsonrpc":"2.0","id":3,"method":"exit"}' %t
#
# RUN: clangd -lit-test -input-mirror-file %t < %s
# RUN: grep '{"jsonrpc":"2.0","id":3,"method":"exit"}' %t
#
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}
---
{"jsonrpc":"2.0","id":3,"method":"shutdown"}
---
{"jsonrpc":"2.0","method":"exit"}
# RUN: clangd -input-style=delimited -sync -input-mirror-file %t < %s
# RUN: grep '{"jsonrpc":"2.0","id":3,"method":"exit"}' %t
#
# RUN: clangd -lit-test -input-mirror-file %t < %s
# RUN: grep '{"jsonrpc":"2.0","id":3,"method":"exit"}' %t
#
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}
---
{"jsonrpc":"2.0","id":3,"method":"shutdown"}
---
{"jsonrpc":"2.0","method":"exit"}
Loading