210 changes: 140 additions & 70 deletions bolt/lib/Rewrite/LinuxKernelRewriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "bolt/Rewrite/MetadataRewriter.h"
#include "bolt/Rewrite/MetadataRewriters.h"
#include "bolt/Utils/CommandLineOpts.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/Support/BinaryStreamWriter.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Debug.h"
Expand All @@ -27,9 +28,9 @@ using namespace bolt;
namespace opts {

static cl::opt<bool>
PrintORC("print-orc",
cl::desc("print ORC unwind information for instructions"),
cl::init(true), cl::Hidden, cl::cat(BoltCategory));
DumpExceptions("dump-linux-exceptions",
cl::desc("dump Linux kernel exception table"),
cl::init(false), cl::Hidden, cl::cat(BoltCategory));

static cl::opt<bool>
DumpORC("dump-orc", cl::desc("dump raw ORC unwind information (sorted)"),
Expand All @@ -40,6 +41,11 @@ static cl::opt<bool> DumpStaticCalls("dump-static-calls",
cl::init(false), cl::Hidden,
cl::cat(BoltCategory));

static cl::opt<bool>
PrintORC("print-orc",
cl::desc("print ORC unwind information for instructions"),
cl::init(true), cl::Hidden, cl::cat(BoltCategory));

} // namespace opts

/// Linux Kernel supports stack unwinding using ORC (oops rewind capability).
Expand Down Expand Up @@ -134,6 +140,13 @@ class LinuxKernelRewriter final : public MetadataRewriter {
using StaticCallListType = std::vector<StaticCallInfo>;
StaticCallListType StaticCallEntries;

/// Section containing the Linux exception table.
ErrorOr<BinarySection &> ExceptionsSection = std::errc::bad_address;
static constexpr size_t EXCEPTION_TABLE_ENTRY_SIZE = 12;

/// Functions with exception handling code.
DenseSet<BinaryFunction *> FunctionsWithExceptions;

/// Insert an LKMarker for a given code pointer \p PC from a non-code section
/// \p SectionName.
void insertLKMarker(uint64_t PC, uint64_t SectionOffset,
Expand All @@ -143,9 +156,6 @@ class LinuxKernelRewriter final : public MetadataRewriter {
/// Process linux kernel special sections and their relocations.
void processLKSections();

/// Process special linux kernel section, __ex_table.
void processLKExTable();

/// Process special linux kernel section, .pci_fixup.
void processLKPCIFixup();

Expand Down Expand Up @@ -174,6 +184,9 @@ class LinuxKernelRewriter final : public MetadataRewriter {
Error readStaticCalls();
Error rewriteStaticCalls();

Error readExceptionTable();
Error rewriteExceptionTable();

/// Mark instructions referenced by kernel metadata.
Error markInstructions();

Expand All @@ -192,6 +205,9 @@ class LinuxKernelRewriter final : public MetadataRewriter {
if (Error E = readStaticCalls())
return E;

if (Error E = readExceptionTable())
return E;

return Error::success();
}

Expand All @@ -203,6 +219,11 @@ class LinuxKernelRewriter final : public MetadataRewriter {
}

Error preEmitFinalizer() override {
// Since rewriteExceptionTable() can mark functions as non-simple, run it
// before other rewriters that depend on simple/emit status.
if (Error E = rewriteExceptionTable())
return E;

if (Error E = rewriteORCTables())
return E;

Expand Down Expand Up @@ -249,77 +270,13 @@ void LinuxKernelRewriter::insertLKMarker(uint64_t PC, uint64_t SectionOffset,
}

void LinuxKernelRewriter::processLKSections() {
processLKExTable();
processLKPCIFixup();
processLKKSymtab();
processLKKSymtab(true);
processLKBugTable();
processLKSMPLocks();
}

/// Process __ex_table section of Linux Kernel.
/// This section contains information regarding kernel level exception
/// handling (https://www.kernel.org/doc/html/latest/x86/exception-tables.html).
/// More documentation is in arch/x86/include/asm/extable.h.
///
/// The section is the list of the following structures:
///
/// struct exception_table_entry {
/// int insn;
/// int fixup;
/// int handler;
/// };
///
void LinuxKernelRewriter::processLKExTable() {
ErrorOr<BinarySection &> SectionOrError =
BC.getUniqueSectionByName("__ex_table");
if (!SectionOrError)
return;

const uint64_t SectionSize = SectionOrError->getSize();
const uint64_t SectionAddress = SectionOrError->getAddress();
assert((SectionSize % 12) == 0 &&
"The size of the __ex_table section should be a multiple of 12");
for (uint64_t I = 0; I < SectionSize; I += 4) {
const uint64_t EntryAddress = SectionAddress + I;
ErrorOr<uint64_t> Offset = BC.getSignedValueAtAddress(EntryAddress, 4);
assert(Offset && "failed reading PC-relative offset for __ex_table");
int32_t SignedOffset = *Offset;
const uint64_t RefAddress = EntryAddress + SignedOffset;

BinaryFunction *ContainingBF =
BC.getBinaryFunctionContainingAddress(RefAddress);
if (!ContainingBF)
continue;

MCSymbol *ReferencedSymbol = ContainingBF->getSymbol();
const uint64_t FunctionOffset = RefAddress - ContainingBF->getAddress();
switch (I % 12) {
default:
llvm_unreachable("bad alignment of __ex_table");
break;
case 0:
// insn
insertLKMarker(RefAddress, I, SignedOffset, true, "__ex_table");
break;
case 4:
// fixup
if (FunctionOffset)
ReferencedSymbol = ContainingBF->addEntryPointAtOffset(FunctionOffset);
BC.addRelocation(EntryAddress, ReferencedSymbol, Relocation::getPC32(), 0,
*Offset);
break;
case 8:
// handler
assert(!FunctionOffset &&
"__ex_table handler entry should point to function start");
BC.addRelocation(EntryAddress, ReferencedSymbol, Relocation::getPC32(), 0,
*Offset);
break;
}
}
}

/// Process .pci_fixup section of Linux Kernel.
/// This section contains a list of entries for different PCI devices and their
/// corresponding hook handler (code pointer where the fixup
Expand Down Expand Up @@ -943,6 +900,119 @@ Error LinuxKernelRewriter::rewriteStaticCalls() {
return Error::success();
}

/// Instructions that access user-space memory can cause page faults. These
/// faults will be handled by the kernel and execution will resume at the fixup
/// code location if the address was invalid. The kernel uses the exception
/// table to match the faulting instruction to its fixup. The table consists of
/// the following entries:
///
/// struct exception_table_entry {
/// int insn;
/// int fixup;
/// int data;
/// };
///
/// More info at:
/// https://www.kernel.org/doc/Documentation/x86/exception-tables.txt
Error LinuxKernelRewriter::readExceptionTable() {
ExceptionsSection = BC.getUniqueSectionByName("__ex_table");
if (!ExceptionsSection)
return Error::success();

if (ExceptionsSection->getSize() % EXCEPTION_TABLE_ENTRY_SIZE)
return createStringError(errc::executable_format_error,
"exception table size error");

const uint64_t SectionAddress = ExceptionsSection->getAddress();
DataExtractor DE(ExceptionsSection->getContents(),
BC.AsmInfo->isLittleEndian(),
BC.AsmInfo->getCodePointerSize());
DataExtractor::Cursor Cursor(0);
uint32_t EntryID = 0;
while (Cursor && Cursor.tell() < ExceptionsSection->getSize()) {
const uint64_t InstAddress =
SectionAddress + Cursor.tell() + (int32_t)DE.getU32(Cursor);
const uint64_t FixupAddress =
SectionAddress + Cursor.tell() + (int32_t)DE.getU32(Cursor);
const uint64_t Data = DE.getU32(Cursor);

// Consume the status of the cursor.
if (!Cursor)
return createStringError(errc::executable_format_error,
"out of bounds while reading exception table");

++EntryID;

if (opts::DumpExceptions) {
BC.outs() << "Exception Entry: " << EntryID << '\n';
BC.outs() << "\tInsn: 0x" << Twine::utohexstr(InstAddress) << '\n'
<< "\tFixup: 0x" << Twine::utohexstr(FixupAddress) << '\n'
<< "\tData: 0x" << Twine::utohexstr(Data) << '\n';
}

MCInst *Inst = nullptr;
MCSymbol *FixupLabel = nullptr;

BinaryFunction *InstBF = BC.getBinaryFunctionContainingAddress(InstAddress);
if (InstBF && BC.shouldEmit(*InstBF)) {
Inst = InstBF->getInstructionAtOffset(InstAddress - InstBF->getAddress());
if (!Inst)
return createStringError(errc::executable_format_error,
"no instruction at address 0x%" PRIx64
" in exception table",
InstAddress);
BC.MIB->addAnnotation(*Inst, "ExceptionEntry", EntryID);
FunctionsWithExceptions.insert(InstBF);
}

if (!InstBF && opts::Verbosity) {
BC.outs() << "BOLT-INFO: no function matches instruction at 0x"
<< Twine::utohexstr(InstAddress)
<< " referenced by Linux exception table\n";
}

BinaryFunction *FixupBF =
BC.getBinaryFunctionContainingAddress(FixupAddress);
if (FixupBF && BC.shouldEmit(*FixupBF)) {
const uint64_t Offset = FixupAddress - FixupBF->getAddress();
if (!FixupBF->getInstructionAtOffset(Offset))
return createStringError(errc::executable_format_error,
"no instruction at fixup address 0x%" PRIx64
" in exception table",
FixupAddress);
FixupLabel = Offset ? FixupBF->addEntryPointAtOffset(Offset)
: FixupBF->getSymbol();
if (Inst)
BC.MIB->addAnnotation(*Inst, "Fixup", FixupLabel->getName());
FunctionsWithExceptions.insert(FixupBF);
}

if (!FixupBF && opts::Verbosity) {
BC.outs() << "BOLT-INFO: no function matches fixup code at 0x"
<< Twine::utohexstr(FixupAddress)
<< " referenced by Linux exception table\n";
}
}

BC.outs() << "BOLT-INFO: parsed "
<< ExceptionsSection->getSize() / EXCEPTION_TABLE_ENTRY_SIZE
<< " exception table entries\n";

return Error::success();
}

/// Depending on the value of CONFIG_BUILDTIME_TABLE_SORT, the kernel expects
/// the exception table to be sorted. Hence we have to sort it after code
/// reordering.
Error LinuxKernelRewriter::rewriteExceptionTable() {
// Disable output of functions with exceptions before rewrite support is
// added.
for (BinaryFunction *BF : FunctionsWithExceptions)
BF->setSimple(false);

return Error::success();
}

} // namespace

std::unique_ptr<MetadataRewriter>
Expand Down
3 changes: 3 additions & 0 deletions bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1629,6 +1629,9 @@ class AArch64MCPlusBuilder : public MCPlusBuilder {
uint64_t RelType;
if (Fixup.getKind() == MCFixupKind(AArch64::fixup_aarch64_pcrel_call26))
RelType = ELF::R_AARCH64_CALL26;
else if (Fixup.getKind() ==
MCFixupKind(AArch64::fixup_aarch64_pcrel_branch26))
RelType = ELF::R_AARCH64_JUMP26;
else if (FKI.Flags & MCFixupKindInfo::FKF_IsPCRel) {
switch (FKI.TargetSize) {
default:
Expand Down
64 changes: 64 additions & 0 deletions bolt/test/X86/linux-exceptions.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# REQUIRES: system-linux

## Check that BOLT correctly parses the Linux kernel exception table.

# 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

## Verify exception bindings to instructions.

# RUN: llvm-bolt %t.exe --print-normalized -o %t.out --keep-nops=0 \
# RUN: --bolt-info=0 | FileCheck %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

# CHECK: BOLT-INFO: Linux kernel binary detected
# CHECK: BOLT-INFO: parsed 2 exception table entries

.text
.globl _start
.type _start, %function
_start:
# CHECK: Binary Function "_start"
nop
.L0:
mov (%rdi), %rax
# CHECK: mov
# CHECK-SAME: ExceptionEntry: 1 # Fixup: [[FIXUP:[a-zA-Z0-9_]+]]
nop
.L1:
mov (%rsi), %rax
# CHECK: mov
# CHECK-SAME: ExceptionEntry: 2 # Fixup: [[FIXUP]]
nop
ret
.LF0:
# CHECK: Secondary Entry Point: [[FIXUP]]
jmp foo
.size _start, .-_start

.globl foo
.type foo, %function
foo:
ret
.size foo, .-foo


## Exception table.
.section __ex_table,"a",@progbits
.align 4

.long .L0 - . # instruction
.long .LF0 - . # fixup
.long 0 # data

.long .L1 - . # instruction
.long .LF0 - . # fixup
.long 0 # data

## Fake Linux Kernel sections.
.section __ksymtab,"a",@progbits
.section __ksymtab_gpl,"a",@progbits
43 changes: 43 additions & 0 deletions bolt/unittests/Core/BinaryContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,49 @@ TEST_P(BinaryContextTester, FlushPendingRelocCALL26) {
EXPECT_FALSE(memcmp(Func2Call, &Vect[12], 4)) << "Wrong forward call value\n";
}

TEST_P(BinaryContextTester, FlushPendingRelocJUMP26) {
if (GetParam() != Triple::aarch64)
GTEST_SKIP();

// This test checks that encodeValueAArch64 used by flushPendingRelocations
// returns correctly encoded values for R_AARCH64_JUMP26 relocation for both
// backward and forward branches.
//
// The offsets layout is:
// 4: func1
// 8: b func1
// 12: b func2
// 16: func2

const uint64_t Size = 20;
char *Data = new char[Size];
BinarySection &BS = BC->registerOrUpdateSection(
".text", ELF::SHT_PROGBITS, ELF::SHF_EXECINSTR | ELF::SHF_ALLOC,
(uint8_t *)Data, Size, 4);
MCSymbol *RelSymbol1 = BC->getOrCreateGlobalSymbol(4, "Func1");
ASSERT_TRUE(RelSymbol1);
BS.addRelocation(8, RelSymbol1, ELF::R_AARCH64_JUMP26, 0, 0, true);
MCSymbol *RelSymbol2 = BC->getOrCreateGlobalSymbol(16, "Func2");
ASSERT_TRUE(RelSymbol2);
BS.addRelocation(12, RelSymbol2, ELF::R_AARCH64_JUMP26, 0, 0, true);

std::error_code EC;
SmallVector<char> Vect(Size);
raw_svector_ostream OS(Vect);

BS.flushPendingRelocations(OS, [&](const MCSymbol *S) {
return S == RelSymbol1 ? 4 : S == RelSymbol2 ? 16 : 0;
});

const uint8_t Func1Call[4] = {255, 255, 255, 23};
const uint8_t Func2Call[4] = {1, 0, 0, 20};

EXPECT_FALSE(memcmp(Func1Call, &Vect[8], 4))
<< "Wrong backward branch value\n";
EXPECT_FALSE(memcmp(Func2Call, &Vect[12], 4))
<< "Wrong forward branch value\n";
}

#endif

TEST_P(BinaryContextTester, BaseAddress) {
Expand Down
3 changes: 3 additions & 0 deletions clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "ChainedComparisonCheck.h"
#include "ComparePointerToMemberVirtualFunctionCheck.h"
#include "CopyConstructorInitCheck.h"
#include "CrtpConstructorAccessibilityCheck.h"
#include "DanglingHandleCheck.h"
#include "DynamicStaticInitializersCheck.h"
#include "EasilySwappableParametersCheck.h"
Expand Down Expand Up @@ -237,6 +238,8 @@ class BugproneModule : public ClangTidyModule {
"bugprone-unhandled-exception-at-new");
CheckFactories.registerCheck<UniquePtrArrayMismatchCheck>(
"bugprone-unique-ptr-array-mismatch");
CheckFactories.registerCheck<CrtpConstructorAccessibilityCheck>(
"bugprone-crtp-constructor-accessibility");
CheckFactories.registerCheck<UnsafeFunctionsCheck>(
"bugprone-unsafe-functions");
CheckFactories.registerCheck<UnusedLocalNonTrivialVariableCheck>(
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ add_clang_library(clangTidyBugproneModule
UnhandledExceptionAtNewCheck.cpp
UnhandledSelfAssignmentCheck.cpp
UniquePtrArrayMismatchCheck.cpp
CrtpConstructorAccessibilityCheck.cpp
UnsafeFunctionsCheck.cpp
UnusedLocalNonTrivialVariableCheck.cpp
UnusedRaiiCheck.cpp
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
//===--- CrtpConstructorAccessibilityCheck.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 "CrtpConstructorAccessibilityCheck.h"
#include "../utils/LexerUtils.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"

using namespace clang::ast_matchers;

namespace clang::tidy::bugprone {

static bool hasPrivateConstructor(const CXXRecordDecl *RD) {
return llvm::any_of(RD->ctors(), [](const CXXConstructorDecl *Ctor) {
return Ctor->getAccess() == AS_private;
});
}

static bool isDerivedParameterBefriended(const CXXRecordDecl *CRTP,
const NamedDecl *Param) {
return llvm::any_of(CRTP->friends(), [&](const FriendDecl *Friend) {
const TypeSourceInfo *const FriendType = Friend->getFriendType();
if (!FriendType) {
return false;
}

const auto *const TTPT =
dyn_cast<TemplateTypeParmType>(FriendType->getType());

return TTPT && TTPT->getDecl() == Param;
});
}

static bool isDerivedClassBefriended(const CXXRecordDecl *CRTP,
const CXXRecordDecl *Derived) {
return llvm::any_of(CRTP->friends(), [&](const FriendDecl *Friend) {
const TypeSourceInfo *const FriendType = Friend->getFriendType();
if (!FriendType) {
return false;
}

return FriendType->getType()->getAsCXXRecordDecl() == Derived;
});
}

static const NamedDecl *
getDerivedParameter(const ClassTemplateSpecializationDecl *CRTP,
const CXXRecordDecl *Derived) {
size_t Idx = 0;
const bool AnyOf = llvm::any_of(
CRTP->getTemplateArgs().asArray(), [&](const TemplateArgument &Arg) {
++Idx;
return Arg.getKind() == TemplateArgument::Type &&
Arg.getAsType()->getAsCXXRecordDecl() == Derived;
});

return AnyOf ? CRTP->getSpecializedTemplate()
->getTemplateParameters()
->getParam(Idx - 1)
: nullptr;
}

static std::vector<FixItHint>
hintMakeCtorPrivate(const CXXConstructorDecl *Ctor,
const std::string &OriginalAccess) {
std::vector<FixItHint> Hints;

Hints.emplace_back(FixItHint::CreateInsertion(
Ctor->getBeginLoc().getLocWithOffset(-1), "private:\n"));

const ASTContext &ASTCtx = Ctor->getASTContext();
const SourceLocation CtorEndLoc =
Ctor->isExplicitlyDefaulted()
? utils::lexer::findNextTerminator(Ctor->getEndLoc(),
ASTCtx.getSourceManager(),
ASTCtx.getLangOpts())
: Ctor->getEndLoc();
Hints.emplace_back(FixItHint::CreateInsertion(
CtorEndLoc.getLocWithOffset(1), '\n' + OriginalAccess + ':' + '\n'));

return Hints;
}

void CrtpConstructorAccessibilityCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(
classTemplateSpecializationDecl(
decl().bind("crtp"),
hasAnyTemplateArgument(refersToType(recordType(hasDeclaration(
cxxRecordDecl(
isDerivedFrom(cxxRecordDecl(equalsBoundNode("crtp"))))
.bind("derived")))))),
this);
}

void CrtpConstructorAccessibilityCheck::check(
const MatchFinder::MatchResult &Result) {
const auto *CRTPInstantiation =
Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>("crtp");
const auto *DerivedRecord = Result.Nodes.getNodeAs<CXXRecordDecl>("derived");
const CXXRecordDecl *CRTPDeclaration =
CRTPInstantiation->getSpecializedTemplate()->getTemplatedDecl();

if (!CRTPDeclaration->hasDefinition()) {
return;
}

const auto *DerivedTemplateParameter =
getDerivedParameter(CRTPInstantiation, DerivedRecord);

assert(DerivedTemplateParameter &&
"No template parameter corresponds to the derived class of the CRTP.");

bool NeedsFriend = !isDerivedParameterBefriended(CRTPDeclaration,
DerivedTemplateParameter) &&
!isDerivedClassBefriended(CRTPDeclaration, DerivedRecord);

const FixItHint HintFriend = FixItHint::CreateInsertion(
CRTPDeclaration->getBraceRange().getEnd(),
"friend " + DerivedTemplateParameter->getNameAsString() + ';' + '\n');

if (hasPrivateConstructor(CRTPDeclaration) && NeedsFriend) {
diag(CRTPDeclaration->getLocation(),
"the CRTP cannot be constructed from the derived class; consider "
"declaring the derived class as friend")
<< HintFriend;
}

auto WithFriendHintIfNeeded =
[&](const DiagnosticBuilder &Diag,
bool NeedsFriend) -> const DiagnosticBuilder & {
if (NeedsFriend)
Diag << HintFriend;

return Diag;
};

if (!CRTPDeclaration->hasUserDeclaredConstructor()) {
const bool IsStruct = CRTPDeclaration->isStruct();

WithFriendHintIfNeeded(
diag(CRTPDeclaration->getLocation(),
"the implicit default constructor of the CRTP is publicly "
"accessible; consider making it private%select{| and declaring "
"the derived class as friend}0")
<< NeedsFriend
<< FixItHint::CreateInsertion(
CRTPDeclaration->getBraceRange().getBegin().getLocWithOffset(
1),
(IsStruct ? "\nprivate:\n" : "\n") +
CRTPDeclaration->getNameAsString() + "() = default;\n" +
(IsStruct ? "public:\n" : "")),
NeedsFriend);
}

for (auto &&Ctor : CRTPDeclaration->ctors()) {
if (Ctor->getAccess() == AS_private)
continue;

const bool IsPublic = Ctor->getAccess() == AS_public;
const std::string Access = IsPublic ? "public" : "protected";

WithFriendHintIfNeeded(
diag(Ctor->getLocation(),
"%0 contructor allows the CRTP to be %select{inherited "
"from|constructed}1 as a regular template class; consider making "
"it private%select{| and declaring the derived class as friend}2")
<< Access << IsPublic << NeedsFriend
<< hintMakeCtorPrivate(Ctor, Access),
NeedsFriend);
}
}

bool CrtpConstructorAccessibilityCheck::isLanguageVersionSupported(
const LangOptions &LangOpts) const {
return LangOpts.CPlusPlus11;
}
} // namespace clang::tidy::bugprone
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//===--- CrtpConstructorAccessibilityCheck.h - clang-tidy -------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_CRTPCONSTRUCTORACCESSIBILITYCHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_CRTPCONSTRUCTORACCESSIBILITYCHECK_H

#include "../ClangTidyCheck.h"

namespace clang::tidy::bugprone {

/// Detects error-prone Curiously Recurring Template Pattern usage, when the
/// CRTP can be constructed outside itself and the derived class.
///
/// For the user-facing documentation see:
/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/crtp-constructor-accessibility.html
class CrtpConstructorAccessibilityCheck : public ClangTidyCheck {
public:
CrtpConstructorAccessibilityCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context) {}
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override;
};

} // namespace clang::tidy::bugprone

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_CRTPCONSTRUCTORACCESSIBILITYCHECK_H
202 changes: 101 additions & 101 deletions clang-tools-extra/clang-tidy/bugprone/UnusedReturnValueCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,98 +33,98 @@ AST_MATCHER_P(FunctionDecl, isInstantiatedFrom, Matcher<FunctionDecl>,
UnusedReturnValueCheck::UnusedReturnValueCheck(llvm::StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
CheckedFunctions(Options.get("CheckedFunctions",
"::std::async;"
"::std::launder;"
"::std::remove;"
"::std::remove_if;"
"::std::unique;"
"::std::unique_ptr::release;"
"::std::basic_string::empty;"
"::std::vector::empty;"
"::std::back_inserter;"
"::std::distance;"
"::std::find;"
"::std::find_if;"
"::std::inserter;"
"::std::lower_bound;"
"::std::make_pair;"
"::std::map::count;"
"::std::map::find;"
"::std::map::lower_bound;"
"::std::multimap::equal_range;"
"::std::multimap::upper_bound;"
"::std::set::count;"
"::std::set::find;"
"::std::setfill;"
"::std::setprecision;"
"::std::setw;"
"::std::upper_bound;"
"::std::vector::at;"
// C standard library
"::bsearch;"
"::ferror;"
"::feof;"
"::isalnum;"
"::isalpha;"
"::isblank;"
"::iscntrl;"
"::isdigit;"
"::isgraph;"
"::islower;"
"::isprint;"
"::ispunct;"
"::isspace;"
"::isupper;"
"::iswalnum;"
"::iswprint;"
"::iswspace;"
"::isxdigit;"
"::memchr;"
"::memcmp;"
"::strcmp;"
"::strcoll;"
"::strncmp;"
"::strpbrk;"
"::strrchr;"
"::strspn;"
"::strstr;"
"::wcscmp;"
// POSIX
"::access;"
"::bind;"
"::connect;"
"::difftime;"
"::dlsym;"
"::fnmatch;"
"::getaddrinfo;"
"::getopt;"
"::htonl;"
"::htons;"
"::iconv_open;"
"::inet_addr;"
"::isascii;"
"::isatty;"
"::mmap;"
"::newlocale;"
"::openat;"
"::pathconf;"
"::pthread_equal;"
"::pthread_getspecific;"
"::pthread_mutex_trylock;"
"::readdir;"
"::readlink;"
"::recvmsg;"
"::regexec;"
"::scandir;"
"::semget;"
"::setjmp;"
"::shm_open;"
"::shmget;"
"::sigismember;"
"::strcasecmp;"
"::strsignal;"
"::ttyname")),
CheckedFunctions(utils::options::parseStringList(
Options.get("CheckedFunctions", "::std::async;"
"::std::launder;"
"::std::remove;"
"::std::remove_if;"
"::std::unique;"
"::std::unique_ptr::release;"
"::std::basic_string::empty;"
"::std::vector::empty;"
"::std::back_inserter;"
"::std::distance;"
"::std::find;"
"::std::find_if;"
"::std::inserter;"
"::std::lower_bound;"
"::std::make_pair;"
"::std::map::count;"
"::std::map::find;"
"::std::map::lower_bound;"
"::std::multimap::equal_range;"
"::std::multimap::upper_bound;"
"::std::set::count;"
"::std::set::find;"
"::std::setfill;"
"::std::setprecision;"
"::std::setw;"
"::std::upper_bound;"
"::std::vector::at;"
// C standard library
"::bsearch;"
"::ferror;"
"::feof;"
"::isalnum;"
"::isalpha;"
"::isblank;"
"::iscntrl;"
"::isdigit;"
"::isgraph;"
"::islower;"
"::isprint;"
"::ispunct;"
"::isspace;"
"::isupper;"
"::iswalnum;"
"::iswprint;"
"::iswspace;"
"::isxdigit;"
"::memchr;"
"::memcmp;"
"::strcmp;"
"::strcoll;"
"::strncmp;"
"::strpbrk;"
"::strrchr;"
"::strspn;"
"::strstr;"
"::wcscmp;"
// POSIX
"::access;"
"::bind;"
"::connect;"
"::difftime;"
"::dlsym;"
"::fnmatch;"
"::getaddrinfo;"
"::getopt;"
"::htonl;"
"::htons;"
"::iconv_open;"
"::inet_addr;"
"::isascii;"
"::isatty;"
"::mmap;"
"::newlocale;"
"::openat;"
"::pathconf;"
"::pthread_equal;"
"::pthread_getspecific;"
"::pthread_mutex_trylock;"
"::readdir;"
"::readlink;"
"::recvmsg;"
"::regexec;"
"::scandir;"
"::semget;"
"::setjmp;"
"::shm_open;"
"::shmget;"
"::sigismember;"
"::strcasecmp;"
"::strsignal;"
"::ttyname"))),
CheckedReturnTypes(utils::options::parseStringList(
Options.get("CheckedReturnTypes", "::std::error_code;"
"::std::error_condition;"
Expand All @@ -133,36 +133,36 @@ UnusedReturnValueCheck::UnusedReturnValueCheck(llvm::StringRef Name,
"::boost::system::error_code"))),
AllowCastToVoid(Options.get("AllowCastToVoid", false)) {}

UnusedReturnValueCheck::UnusedReturnValueCheck(llvm::StringRef Name,
ClangTidyContext *Context,
std::string CheckedFunctions)
UnusedReturnValueCheck::UnusedReturnValueCheck(
llvm::StringRef Name, ClangTidyContext *Context,
std::vector<StringRef> CheckedFunctions)
: UnusedReturnValueCheck(Name, Context, std::move(CheckedFunctions), {},
false) {}

UnusedReturnValueCheck::UnusedReturnValueCheck(
llvm::StringRef Name, ClangTidyContext *Context,
std::string CheckedFunctions, std::vector<StringRef> CheckedReturnTypes,
bool AllowCastToVoid)
std::vector<StringRef> CheckedFunctions,
std::vector<StringRef> CheckedReturnTypes, bool AllowCastToVoid)
: ClangTidyCheck(Name, Context),
CheckedFunctions(std::move(CheckedFunctions)),
CheckedReturnTypes(std::move(CheckedReturnTypes)),
AllowCastToVoid(AllowCastToVoid) {}

void UnusedReturnValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "CheckedFunctions", CheckedFunctions);
Options.store(Opts, "CheckedFunctions",
utils::options::serializeStringList(CheckedFunctions));
Options.store(Opts, "CheckedReturnTypes",
utils::options::serializeStringList(CheckedReturnTypes));
Options.store(Opts, "AllowCastToVoid", AllowCastToVoid);
}

void UnusedReturnValueCheck::registerMatchers(MatchFinder *Finder) {
auto FunVec = utils::options::parseStringList(CheckedFunctions);

auto MatchedDirectCallExpr =
expr(callExpr(callee(functionDecl(
// Don't match void overloads of checked functions.
unless(returns(voidType())),
anyOf(isInstantiatedFrom(hasAnyName(FunVec)),
anyOf(isInstantiatedFrom(matchers::matchesAnyListedName(
CheckedFunctions)),
returns(hasCanonicalType(hasDeclaration(
namedDecl(matchers::matchesAnyListedName(
CheckedReturnTypes)))))))))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ class UnusedReturnValueCheck : public ClangTidyCheck {
}

private:
std::string CheckedFunctions;
const std::vector<StringRef> CheckedFunctions;
const std::vector<StringRef> CheckedReturnTypes;

protected:
UnusedReturnValueCheck(StringRef Name, ClangTidyContext *Context,
std::string CheckedFunctions);
std::vector<StringRef> CheckedFunctions);
UnusedReturnValueCheck(StringRef Name, ClangTidyContext *Context,
std::string CheckedFunctions,
std::vector<StringRef> CheckedFunctions,
std::vector<StringRef> CheckedReturnTypes,
bool AllowCastToVoid);
bool AllowCastToVoid;
Expand Down
57 changes: 42 additions & 15 deletions clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,8 @@ void UseAfterMoveFinder::getReinits(
traverse(TK_AsIs, DeclRefMatcher),
unless(parmVarDecl(hasType(
references(qualType(isConstQualified())))))),
unless(callee(functionDecl(hasName("::std::move")))))))
unless(callee(functionDecl(
hasAnyName("::std::move", "::std::forward")))))))
.bind("reinit");

Stmts->clear();
Expand Down Expand Up @@ -359,24 +360,46 @@ void UseAfterMoveFinder::getReinits(
}
}

enum class MoveType {
Move, // std::move
Forward, // std::forward
};

static MoveType determineMoveType(const FunctionDecl *FuncDecl) {
if (FuncDecl->getName() == "move")
return MoveType::Move;
if (FuncDecl->getName() == "forward")
return MoveType::Forward;

llvm_unreachable("Invalid move type");
}

static void emitDiagnostic(const Expr *MovingCall, const DeclRefExpr *MoveArg,
const UseAfterMove &Use, ClangTidyCheck *Check,
ASTContext *Context) {
SourceLocation UseLoc = Use.DeclRef->getExprLoc();
SourceLocation MoveLoc = MovingCall->getExprLoc();
ASTContext *Context, MoveType Type) {
const SourceLocation UseLoc = Use.DeclRef->getExprLoc();
const SourceLocation MoveLoc = MovingCall->getExprLoc();

const bool IsMove = (Type == MoveType::Move);

Check->diag(UseLoc, "'%0' used after it was moved")
<< MoveArg->getDecl()->getName();
Check->diag(MoveLoc, "move occurred here", DiagnosticIDs::Note);
Check->diag(UseLoc, "'%0' used after it was %select{forwarded|moved}1")
<< MoveArg->getDecl()->getName() << IsMove;
Check->diag(MoveLoc, "%select{forward|move}0 occurred here",
DiagnosticIDs::Note)
<< IsMove;
if (Use.EvaluationOrderUndefined) {
Check->diag(UseLoc,
"the use and move are unsequenced, i.e. there is no guarantee "
"about the order in which they are evaluated",
DiagnosticIDs::Note);
Check->diag(
UseLoc,
"the use and %select{forward|move}0 are unsequenced, i.e. "
"there is no guarantee about the order in which they are evaluated",
DiagnosticIDs::Note)
<< IsMove;
} else if (UseLoc < MoveLoc || Use.DeclRef == MoveArg) {
Check->diag(UseLoc,
"the use happens in a later loop iteration than the move",
DiagnosticIDs::Note);
"the use happens in a later loop iteration than the "
"%select{forward|move}0",
DiagnosticIDs::Note)
<< IsMove;
}
}

Expand All @@ -388,7 +411,9 @@ void UseAfterMoveCheck::registerMatchers(MatchFinder *Finder) {
auto TryEmplaceMatcher =
cxxMemberCallExpr(callee(cxxMethodDecl(hasName("try_emplace"))));
auto CallMoveMatcher =
callExpr(argumentCountIs(1), callee(functionDecl(hasName("::std::move"))),
callExpr(argumentCountIs(1),
callee(functionDecl(hasAnyName("::std::move", "::std::forward"))
.bind("move-decl")),
hasArgument(0, declRefExpr().bind("arg")),
unless(inDecltypeOrTemplateArg()),
unless(hasParent(TryEmplaceMatcher)), expr().bind("call-move"),
Expand Down Expand Up @@ -436,6 +461,7 @@ void UseAfterMoveCheck::check(const MatchFinder::MatchResult &Result) {
const auto *CallMove = Result.Nodes.getNodeAs<CallExpr>("call-move");
const auto *MovingCall = Result.Nodes.getNodeAs<Expr>("moving-call");
const auto *Arg = Result.Nodes.getNodeAs<DeclRefExpr>("arg");
const auto *MoveDecl = Result.Nodes.getNodeAs<FunctionDecl>("move-decl");

if (!MovingCall || !MovingCall->getExprLoc().isValid())
MovingCall = CallMove;
Expand Down Expand Up @@ -470,7 +496,8 @@ void UseAfterMoveCheck::check(const MatchFinder::MatchResult &Result) {
UseAfterMoveFinder Finder(Result.Context);
UseAfterMove Use;
if (Finder.find(CodeBlock, MovingCall, Arg->getDecl(), &Use))
emitDiagnostic(MovingCall, Arg, Use, this, Result.Context);
emitDiagnostic(MovingCall, Arg, Use, this, Result.Context,
determineMoveType(MoveDecl));
}
}

Expand Down
33 changes: 25 additions & 8 deletions clang-tools-extra/clang-tidy/google/ExplicitConstructorCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,10 @@ static bool isStdInitializerList(QualType Type) {
}

void ExplicitConstructorCheck::check(const MatchFinder::MatchResult &Result) {
constexpr char WarningMessage[] =
constexpr char NoExpressionWarningMessage[] =
"%0 must be marked explicit to avoid unintentional implicit conversions";
constexpr char WithExpressionWarningMessage[] =
"%0 explicit expression evaluates to 'false'";

if (const auto *Conversion =
Result.Nodes.getNodeAs<CXXConversionDecl>("conversion")) {
Expand All @@ -91,7 +93,7 @@ void ExplicitConstructorCheck::check(const MatchFinder::MatchResult &Result) {
// gmock to define matchers).
if (Loc.isMacroID())
return;
diag(Loc, WarningMessage)
diag(Loc, NoExpressionWarningMessage)
<< Conversion << FixItHint::CreateInsertion(Loc, "explicit ");
return;
}
Expand All @@ -101,9 +103,11 @@ void ExplicitConstructorCheck::check(const MatchFinder::MatchResult &Result) {
Ctor->getMinRequiredArguments() > 1)
return;

const ExplicitSpecifier ExplicitSpec = Ctor->getExplicitSpecifier();

bool TakesInitializerList = isStdInitializerList(
Ctor->getParamDecl(0)->getType().getNonReferenceType());
if (Ctor->isExplicit() &&
if (ExplicitSpec.isExplicit() &&
(Ctor->isCopyOrMoveConstructor() || TakesInitializerList)) {
auto IsKwExplicit = [](const Token &Tok) {
return Tok.is(tok::raw_identifier) &&
Expand All @@ -130,18 +134,31 @@ void ExplicitConstructorCheck::check(const MatchFinder::MatchResult &Result) {
return;
}

if (Ctor->isExplicit() || Ctor->isCopyOrMoveConstructor() ||
if (ExplicitSpec.isExplicit() || Ctor->isCopyOrMoveConstructor() ||
TakesInitializerList)
return;

bool SingleArgument =
// Don't complain about explicit(false) or dependent expressions
const Expr *ExplicitExpr = ExplicitSpec.getExpr();
if (ExplicitExpr) {
ExplicitExpr = ExplicitExpr->IgnoreImplicit();
if (isa<CXXBoolLiteralExpr>(ExplicitExpr) ||
ExplicitExpr->isInstantiationDependent())
return;
}

const bool SingleArgument =
Ctor->getNumParams() == 1 && !Ctor->getParamDecl(0)->isParameterPack();
SourceLocation Loc = Ctor->getLocation();
diag(Loc, WarningMessage)
auto Diag =
diag(Loc, ExplicitExpr ? WithExpressionWarningMessage
: NoExpressionWarningMessage)
<< (SingleArgument
? "single-argument constructors"
: "constructors that are callable with a single argument")
<< FixItHint::CreateInsertion(Loc, "explicit ");
: "constructors that are callable with a single argument");

if (!ExplicitExpr)
Diag << FixItHint::CreateInsertion(Loc, "explicit ");
}

} // namespace clang::tidy::google
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ namespace clang::tidy::hicpp {
IgnoredRemoveResultCheck::IgnoredRemoveResultCheck(llvm::StringRef Name,
ClangTidyContext *Context)
: UnusedReturnValueCheck(Name, Context,
"::std::remove;"
"::std::remove_if;"
"::std::unique") {
{
"::std::remove",
"::std::remove_if",
"::std::unique",
}) {
// The constructor for ClangTidyCheck needs to have been called
// before we can access options via Options.get().
AllowCastToVoid = Options.get("AllowCastToVoid", true);
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 @@ -31,6 +31,7 @@ add_clang_library(clangTidyModernizeModule
UseBoolLiteralsCheck.cpp
UseConstraintsCheck.cpp
UseDefaultMemberInitCheck.cpp
UseDesignatedInitializersCheck.cpp
UseEmplaceCheck.cpp
UseEqualsDefaultCheck.cpp
UseEqualsDeleteCheck.cpp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "UseBoolLiteralsCheck.h"
#include "UseConstraintsCheck.h"
#include "UseDefaultMemberInitCheck.h"
#include "UseDesignatedInitializersCheck.h"
#include "UseEmplaceCheck.h"
#include "UseEqualsDefaultCheck.h"
#include "UseEqualsDeleteCheck.h"
Expand Down Expand Up @@ -68,6 +69,8 @@ class ModernizeModule : public ClangTidyModule {
CheckFactories.registerCheck<MakeSharedCheck>("modernize-make-shared");
CheckFactories.registerCheck<MakeUniqueCheck>("modernize-make-unique");
CheckFactories.registerCheck<PassByValueCheck>("modernize-pass-by-value");
CheckFactories.registerCheck<UseDesignatedInitializersCheck>(
"modernize-use-designated-initializers");
CheckFactories.registerCheck<UseStartsEndsWithCheck>(
"modernize-use-starts-ends-with");
CheckFactories.registerCheck<UseStdNumbersCheck>(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
//===--- UseDesignatedInitializersCheck.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 "UseDesignatedInitializersCheck.h"
#include "../utils/DesignatedInitializers.h"
#include "clang/AST/APValue.h"
#include "clang/AST/Decl.h"
#include "clang/AST/Expr.h"
#include "clang/AST/Stmt.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchersMacros.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Lex/Lexer.h"

using namespace clang::ast_matchers;

namespace clang::tidy::modernize {

static constexpr char IgnoreSingleElementAggregatesName[] =
"IgnoreSingleElementAggregates";
static constexpr bool IgnoreSingleElementAggregatesDefault = true;

static constexpr char RestrictToPODTypesName[] = "RestrictToPODTypes";
static constexpr bool RestrictToPODTypesDefault = false;

static constexpr char IgnoreMacrosName[] = "IgnoreMacros";
static constexpr bool IgnoreMacrosDefault = true;

namespace {

struct Designators {

Designators(const InitListExpr *InitList) : InitList(InitList) {
assert(InitList->isSyntacticForm());
};

unsigned size() { return getCached().size(); }

std::optional<llvm::StringRef> operator[](const SourceLocation &Location) {
const auto &Designators = getCached();
const auto Result = Designators.find(Location);
if (Result == Designators.end())
return {};
const llvm::StringRef Designator = Result->getSecond();
return (Designator.front() == '.' ? Designator.substr(1) : Designator)
.trim("\0"); // Trim NULL characters appearing on Windows in the
// name.
}

private:
using LocationToNameMap = llvm::DenseMap<clang::SourceLocation, std::string>;

std::optional<LocationToNameMap> CachedDesignators;
const InitListExpr *InitList;

LocationToNameMap &getCached() {
return CachedDesignators ? *CachedDesignators
: CachedDesignators.emplace(
utils::getUnwrittenDesignators(InitList));
}
};

unsigned getNumberOfDesignated(const InitListExpr *SyntacticInitList) {
return llvm::count_if(*SyntacticInitList, [](auto *InitExpr) {
return isa<DesignatedInitExpr>(InitExpr);
});
}

AST_MATCHER(CXXRecordDecl, isAggregate) { return Node.isAggregate(); }

AST_MATCHER(CXXRecordDecl, isPOD) { return Node.isPOD(); }

AST_MATCHER(InitListExpr, isFullyDesignated) {
if (const InitListExpr *SyntacticForm =
Node.isSyntacticForm() ? &Node : Node.getSyntacticForm())
return getNumberOfDesignated(SyntacticForm) == SyntacticForm->getNumInits();
return true;
}

AST_MATCHER(InitListExpr, hasMoreThanOneElement) {
return Node.getNumInits() > 1;
}

} // namespace

UseDesignatedInitializersCheck::UseDesignatedInitializersCheck(
StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context), IgnoreSingleElementAggregates(Options.get(
IgnoreSingleElementAggregatesName,
IgnoreSingleElementAggregatesDefault)),
RestrictToPODTypes(
Options.get(RestrictToPODTypesName, RestrictToPODTypesDefault)),
IgnoreMacros(
Options.getLocalOrGlobal(IgnoreMacrosName, IgnoreMacrosDefault)) {}

void UseDesignatedInitializersCheck::registerMatchers(MatchFinder *Finder) {
const auto HasBaseWithFields =
hasAnyBase(hasType(cxxRecordDecl(has(fieldDecl()))));
Finder->addMatcher(
initListExpr(
hasType(cxxRecordDecl(RestrictToPODTypes ? isPOD() : isAggregate(),
unless(HasBaseWithFields))
.bind("type")),
IgnoreSingleElementAggregates ? hasMoreThanOneElement() : anything(),
unless(isFullyDesignated()))
.bind("init"),
this);
}

void UseDesignatedInitializersCheck::check(
const MatchFinder::MatchResult &Result) {
const auto *InitList = Result.Nodes.getNodeAs<InitListExpr>("init");
const auto *Type = Result.Nodes.getNodeAs<CXXRecordDecl>("type");
if (!Type || !InitList)
return;
const auto *SyntacticInitList = InitList->getSyntacticForm();
if (!SyntacticInitList)
return;
Designators Designators{SyntacticInitList};
const unsigned NumberOfDesignated = getNumberOfDesignated(SyntacticInitList);
if (SyntacticInitList->getNumInits() - NumberOfDesignated >
Designators.size())
return;

// If the whole initializer list is un-designated, issue only one warning and
// a single fix-it for the whole expression.
if (0 == NumberOfDesignated) {
if (IgnoreMacros && InitList->getBeginLoc().isMacroID())
return;
{
DiagnosticBuilder Diag =
diag(InitList->getLBraceLoc(),
"use designated initializer list to initialize %0");
Diag << Type << InitList->getSourceRange();
for (const Stmt *InitExpr : *SyntacticInitList) {
const auto Designator = Designators[InitExpr->getBeginLoc()];
if (Designator && !Designator->empty())
Diag << FixItHint::CreateInsertion(InitExpr->getBeginLoc(),
("." + *Designator + "=").str());
}
}
diag(Type->getBeginLoc(), "aggregate type is defined here",
DiagnosticIDs::Note);
return;
}

// In case that only a few elements are un-designated (not all as before), the
// check offers dedicated issues and fix-its for each of them.
for (const auto *InitExpr : *SyntacticInitList) {
if (isa<DesignatedInitExpr>(InitExpr))
continue;
if (IgnoreMacros && InitExpr->getBeginLoc().isMacroID())
continue;
const auto Designator = Designators[InitExpr->getBeginLoc()];
if (!Designator || Designator->empty()) {
// There should always be a designator. If there's unexpectedly none, we
// at least report a generic diagnostic.
diag(InitExpr->getBeginLoc(), "use designated init expression")
<< InitExpr->getSourceRange();
} else {
diag(InitExpr->getBeginLoc(),
"use designated init expression to initialize field '%0'")
<< InitExpr->getSourceRange() << *Designator
<< FixItHint::CreateInsertion(InitExpr->getBeginLoc(),
("." + *Designator + "=").str());
}
}
}

void UseDesignatedInitializersCheck::storeOptions(
ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, IgnoreSingleElementAggregatesName,
IgnoreSingleElementAggregates);
Options.store(Opts, RestrictToPODTypesName, RestrictToPODTypes);
Options.store(Opts, IgnoreMacrosName, IgnoreMacros);
}

} // namespace clang::tidy::modernize
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//===--- UseDesignatedInitializersCheck.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_USEDESIGNATEDINITIALIZERSCHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEDESIGNATEDINITIALIZERSCHECK_H

#include "../ClangTidyCheck.h"

namespace clang::tidy::modernize {

/// Finds initializer lists for aggregate type that could be
/// written as designated initializers instead.
///
/// For the user-facing documentation see:
/// http://clang.llvm.org/extra/clang-tidy/checks/modernize/use-designated-initializers.html
class UseDesignatedInitializersCheck : public ClangTidyCheck {
public:
UseDesignatedInitializersCheck(StringRef Name, ClangTidyContext *Context);
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;

std::optional<TraversalKind> getCheckTraversalKind() const override {
return TK_IgnoreUnlessSpelledInSource;
}

private:
bool IgnoreSingleElementAggregates;
bool RestrictToPODTypes;
bool IgnoreMacros;
};

} // namespace clang::tidy::modernize

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEDESIGNATEDINITIALIZERSCHECK_H
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/utils/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ add_clang_library(clangTidyUtils
Aliasing.cpp
ASTUtils.cpp
DeclRefExprUtils.cpp
DesignatedInitializers.cpp
ExceptionAnalyzer.cpp
ExceptionSpecAnalyzer.cpp
ExprSequence.cpp
Expand Down
195 changes: 195 additions & 0 deletions clang-tools-extra/clang-tidy/utils/DesignatedInitializers.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
//===--- DesignatedInitializers.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
//
//===----------------------------------------------------------------------===//
///
/// \file
/// This file provides utilities for designated initializers.
///
//===----------------------------------------------------------------------===//

#include "DesignatedInitializers.h"
#include "clang/AST/DeclCXX.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/ScopeExit.h"

namespace clang::tidy::utils {

namespace {

/// Returns true if Name is reserved, like _Foo or __Vector_base.
static inline bool isReservedName(llvm::StringRef Name) {
// This doesn't catch all cases, but the most common.
return Name.size() >= 2 && Name[0] == '_' &&
(isUppercase(Name[1]) || Name[1] == '_');
}

// Helper class to iterate over the designator names of an aggregate type.
//
// For an array type, yields [0], [1], [2]...
// For aggregate classes, yields null for each base, then .field1, .field2,
// ...
class AggregateDesignatorNames {
public:
AggregateDesignatorNames(QualType T) {
if (!T.isNull()) {
T = T.getCanonicalType();
if (T->isArrayType()) {
IsArray = true;
Valid = true;
return;
}
if (const RecordDecl *RD = T->getAsRecordDecl()) {
Valid = true;
FieldsIt = RD->field_begin();
FieldsEnd = RD->field_end();
if (const auto *CRD = llvm::dyn_cast<CXXRecordDecl>(RD)) {
BasesIt = CRD->bases_begin();
BasesEnd = CRD->bases_end();
Valid = CRD->isAggregate();
}
OneField = Valid && BasesIt == BasesEnd && FieldsIt != FieldsEnd &&
std::next(FieldsIt) == FieldsEnd;
}
}
}
// Returns false if the type was not an aggregate.
operator bool() { return Valid; }
// Advance to the next element in the aggregate.
void next() {
if (IsArray)
++Index;
else if (BasesIt != BasesEnd)
++BasesIt;
else if (FieldsIt != FieldsEnd)
++FieldsIt;
}
// Print the designator to Out.
// Returns false if we could not produce a designator for this element.
bool append(std::string &Out, bool ForSubobject) {
if (IsArray) {
Out.push_back('[');
Out.append(std::to_string(Index));
Out.push_back(']');
return true;
}
if (BasesIt != BasesEnd)
return false; // Bases can't be designated. Should we make one up?
if (FieldsIt != FieldsEnd) {
llvm::StringRef FieldName;
if (const IdentifierInfo *II = FieldsIt->getIdentifier())
FieldName = II->getName();

// For certain objects, their subobjects may be named directly.
if (ForSubobject &&
(FieldsIt->isAnonymousStructOrUnion() ||
// std::array<int,3> x = {1,2,3}. Designators not strictly valid!
(OneField && isReservedName(FieldName))))
return true;

if (!FieldName.empty() && !isReservedName(FieldName)) {
Out.push_back('.');
Out.append(FieldName.begin(), FieldName.end());
return true;
}
return false;
}
return false;
}

private:
bool Valid = false;
bool IsArray = false;
bool OneField = false; // e.g. std::array { T __elements[N]; }
unsigned Index = 0;
CXXRecordDecl::base_class_const_iterator BasesIt;
CXXRecordDecl::base_class_const_iterator BasesEnd;
RecordDecl::field_iterator FieldsIt;
RecordDecl::field_iterator FieldsEnd;
};

// Collect designator labels describing the elements of an init list.
//
// This function contributes the designators of some (sub)object, which is
// represented by the semantic InitListExpr Sem.
// This includes any nested subobjects, but *only* if they are part of the
// same original syntactic init list (due to brace elision). In other words,
// it may descend into subobjects but not written init-lists.
//
// For example: struct Outer { Inner a,b; }; struct Inner { int x, y; }
// Outer o{{1, 2}, 3};
// This function will be called with Sem = { {1, 2}, {3, ImplicitValue} }
// It should generate designators '.a:' and '.b.x:'.
// '.a:' is produced directly without recursing into the written sublist.
// (The written sublist will have a separate collectDesignators() call later).
// Recursion with Prefix='.b' and Sem = {3, ImplicitValue} produces '.b.x:'.
void collectDesignators(const InitListExpr *Sem,
llvm::DenseMap<SourceLocation, std::string> &Out,
const llvm::DenseSet<SourceLocation> &NestedBraces,
std::string &Prefix) {
if (!Sem || Sem->isTransparent())
return;
assert(Sem->isSemanticForm());

// The elements of the semantic form all correspond to direct subobjects of
// the aggregate type. `Fields` iterates over these subobject names.
AggregateDesignatorNames Fields(Sem->getType());
if (!Fields)
return;
for (const Expr *Init : Sem->inits()) {
auto Next = llvm::make_scope_exit([&, Size(Prefix.size())] {
Fields.next(); // Always advance to the next subobject name.
Prefix.resize(Size); // Erase any designator we appended.
});
// Skip for a broken initializer or if it is a "hole" in a subobject that
// was not explicitly initialized.
if (!Init || llvm::isa<ImplicitValueInitExpr>(Init))
continue;

const auto *BraceElidedSubobject = llvm::dyn_cast<InitListExpr>(Init);
if (BraceElidedSubobject &&
NestedBraces.contains(BraceElidedSubobject->getLBraceLoc()))
BraceElidedSubobject = nullptr; // there were braces!

if (!Fields.append(Prefix, BraceElidedSubobject != nullptr))
continue; // no designator available for this subobject
if (BraceElidedSubobject) {
// If the braces were elided, this aggregate subobject is initialized
// inline in the same syntactic list.
// Descend into the semantic list describing the subobject.
// (NestedBraces are still correct, they're from the same syntactic
// list).
collectDesignators(BraceElidedSubobject, Out, NestedBraces, Prefix);
continue;
}
Out.try_emplace(Init->getBeginLoc(), Prefix);
}
}

} // namespace

llvm::DenseMap<SourceLocation, std::string>
getUnwrittenDesignators(const InitListExpr *Syn) {
assert(Syn->isSyntacticForm());

// collectDesignators needs to know which InitListExprs in the semantic tree
// were actually written, but InitListExpr::isExplicit() lies.
// Instead, record where braces of sub-init-lists occur in the syntactic form.
llvm::DenseSet<SourceLocation> NestedBraces;
for (const Expr *Init : Syn->inits())
if (auto *Nested = llvm::dyn_cast<InitListExpr>(Init))
NestedBraces.insert(Nested->getLBraceLoc());

// Traverse the semantic form to find the designators.
// We use their SourceLocation to correlate with the syntactic form later.
llvm::DenseMap<SourceLocation, std::string> Designators;
std::string EmptyPrefix;
collectDesignators(Syn->isSemanticForm() ? Syn : Syn->getSemanticForm(),
Designators, NestedBraces, EmptyPrefix);
return Designators;
}

} // namespace clang::tidy::utils
42 changes: 42 additions & 0 deletions clang-tools-extra/clang-tidy/utils/DesignatedInitializers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//===--- DesignatedInitializers.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
//
//===----------------------------------------------------------------------===//
///
/// \file
/// This file provides utilities for designated initializers.
///
//===----------------------------------------------------------------------===//

#include "clang/AST/Expr.h"
#include "clang/Basic/SourceLocation.h"
#include "llvm/ADT/DenseMap.h"

namespace clang::tidy::utils {

/// Get designators describing the elements of a (syntactic) init list.
///
/// Given for example the type
/// \code
/// struct S { int i, j; };
/// \endcode
/// and the definition
/// \code
/// S s{1, 2};
/// \endcode
/// calling `getUnwrittenDesignators` for the initializer list expression
/// `{1, 2}` would produce the map `{loc(1): ".i", loc(2): ".j"}`.
///
/// It does not produce designators for any explicitly-written nested lists,
/// e.g. `{1, .j=2}` would only return `{loc(1): ".i"}`.
///
/// It also considers structs with fields of record types like
/// `struct T { S s; };`. In this case, there would be designators of the
/// form `.s.i` and `.s.j` in the returned map.
llvm::DenseMap<clang::SourceLocation, std::string>
getUnwrittenDesignators(const clang::InitListExpr *Syn);

} // namespace clang::tidy::utils
1 change: 1 addition & 0 deletions clang-tools-extra/clangd/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ target_link_libraries(clangDaemon
clangIncludeCleaner
clangPseudo
clangTidy
clangTidyUtils

clangdSupport
)
Expand Down
170 changes: 138 additions & 32 deletions clang-tools-extra/clangd/HeuristicResolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,80 @@
namespace clang {
namespace clangd {

namespace {

// Helper class for implementing HeuristicResolver.
// Unlike HeuristicResolver which is a long-lived class,
// a new instance of this class is created for every external
// call into a HeuristicResolver operation. That allows this
// class to store state that's local to such a top-level call,
// particularly "recursion protection sets" that keep track of
// nodes that have already been seen to avoid infinite recursion.
class HeuristicResolverImpl {
public:
HeuristicResolverImpl(ASTContext &Ctx) : Ctx(Ctx) {}

// These functions match the public interface of HeuristicResolver
// (but aren't const since they may modify the recursion protection sets).
std::vector<const NamedDecl *>
resolveMemberExpr(const CXXDependentScopeMemberExpr *ME);
std::vector<const NamedDecl *>
resolveDeclRefExpr(const DependentScopeDeclRefExpr *RE);
std::vector<const NamedDecl *> resolveTypeOfCallExpr(const CallExpr *CE);
std::vector<const NamedDecl *> resolveCalleeOfCallExpr(const CallExpr *CE);
std::vector<const NamedDecl *>
resolveUsingValueDecl(const UnresolvedUsingValueDecl *UUVD);
std::vector<const NamedDecl *>
resolveDependentNameType(const DependentNameType *DNT);
std::vector<const NamedDecl *> resolveTemplateSpecializationType(
const DependentTemplateSpecializationType *DTST);
const Type *resolveNestedNameSpecifierToType(const NestedNameSpecifier *NNS);
const Type *getPointeeType(const Type *T);

private:
ASTContext &Ctx;

// Recursion protection sets
llvm::SmallSet<const DependentNameType *, 4> SeenDependentNameTypes;

// Given a tag-decl type and a member name, heuristically resolve the
// name to one or more declarations.
// The current heuristic is simply to look up the name in the primary
// template. This is a heuristic because the template could potentially
// have specializations that declare different members.
// Multiple declarations could be returned if the name is overloaded
// (e.g. an overloaded method in the primary template).
// This heuristic will give the desired answer in many cases, e.g.
// for a call to vector<T>::size().
std::vector<const NamedDecl *>
resolveDependentMember(const Type *T, DeclarationName Name,
llvm::function_ref<bool(const NamedDecl *ND)> Filter);

// Try to heuristically resolve the type of a possibly-dependent expression
// `E`.
const Type *resolveExprToType(const Expr *E);
std::vector<const NamedDecl *> resolveExprToDecls(const Expr *E);

// Helper function for HeuristicResolver::resolveDependentMember()
// which takes a possibly-dependent type `T` and heuristically
// resolves it to a CXXRecordDecl in which we can try name lookup.
CXXRecordDecl *resolveTypeToRecordDecl(const Type *T);

// This is a reimplementation of CXXRecordDecl::lookupDependentName()
// so that the implementation can call into other HeuristicResolver helpers.
// FIXME: Once HeuristicResolver is upstreamed to the clang libraries
// (https://github.com/clangd/clangd/discussions/1662),
// CXXRecordDecl::lookupDepenedentName() can be removed, and its call sites
// can be modified to benefit from the more comprehensive heuristics offered
// by HeuristicResolver instead.
std::vector<const NamedDecl *>
lookupDependentName(CXXRecordDecl *RD, DeclarationName Name,
llvm::function_ref<bool(const NamedDecl *ND)> Filter);
bool findOrdinaryMemberInDependentClasses(const CXXBaseSpecifier *Specifier,
CXXBasePath &Path,
DeclarationName Name);
};

// Convenience lambdas for use as the 'Filter' parameter of
// HeuristicResolver::resolveDependentMember().
const auto NoFilter = [](const NamedDecl *D) { return true; };
Expand All @@ -31,8 +105,6 @@ const auto TemplateFilter = [](const NamedDecl *D) {
return isa<TemplateDecl>(D);
};

namespace {

const Type *resolveDeclsToType(const std::vector<const NamedDecl *> &Decls,
ASTContext &Ctx) {
if (Decls.size() != 1) // Names an overload set -- just bail.
Expand All @@ -46,12 +118,10 @@ const Type *resolveDeclsToType(const std::vector<const NamedDecl *> &Decls,
return nullptr;
}

} // namespace

// Helper function for HeuristicResolver::resolveDependentMember()
// which takes a possibly-dependent type `T` and heuristically
// resolves it to a CXXRecordDecl in which we can try name lookup.
CXXRecordDecl *HeuristicResolver::resolveTypeToRecordDecl(const Type *T) const {
CXXRecordDecl *HeuristicResolverImpl::resolveTypeToRecordDecl(const Type *T) {
assert(T);

// Unwrap type sugar such as type aliases.
Expand Down Expand Up @@ -84,7 +154,7 @@ CXXRecordDecl *HeuristicResolver::resolveTypeToRecordDecl(const Type *T) const {
return TD->getTemplatedDecl();
}

const Type *HeuristicResolver::getPointeeType(const Type *T) const {
const Type *HeuristicResolverImpl::getPointeeType(const Type *T) {
if (!T)
return nullptr;

Expand Down Expand Up @@ -117,8 +187,8 @@ const Type *HeuristicResolver::getPointeeType(const Type *T) const {
return FirstArg.getAsType().getTypePtrOrNull();
}

std::vector<const NamedDecl *> HeuristicResolver::resolveMemberExpr(
const CXXDependentScopeMemberExpr *ME) const {
std::vector<const NamedDecl *> HeuristicResolverImpl::resolveMemberExpr(
const CXXDependentScopeMemberExpr *ME) {
// If the expression has a qualifier, try resolving the member inside the
// qualifier's type.
// Note that we cannot use a NonStaticFilter in either case, for a couple
Expand Down Expand Up @@ -164,14 +234,14 @@ std::vector<const NamedDecl *> HeuristicResolver::resolveMemberExpr(
return resolveDependentMember(BaseType, ME->getMember(), NoFilter);
}

std::vector<const NamedDecl *> HeuristicResolver::resolveDeclRefExpr(
const DependentScopeDeclRefExpr *RE) const {
std::vector<const NamedDecl *>
HeuristicResolverImpl::resolveDeclRefExpr(const DependentScopeDeclRefExpr *RE) {
return resolveDependentMember(RE->getQualifier()->getAsType(),
RE->getDeclName(), StaticFilter);
}

std::vector<const NamedDecl *>
HeuristicResolver::resolveTypeOfCallExpr(const CallExpr *CE) const {
HeuristicResolverImpl::resolveTypeOfCallExpr(const CallExpr *CE) {
const auto *CalleeType = resolveExprToType(CE->getCallee());
if (!CalleeType)
return {};
Expand All @@ -187,37 +257,39 @@ HeuristicResolver::resolveTypeOfCallExpr(const CallExpr *CE) const {
}

std::vector<const NamedDecl *>
HeuristicResolver::resolveCalleeOfCallExpr(const CallExpr *CE) const {
HeuristicResolverImpl::resolveCalleeOfCallExpr(const CallExpr *CE) {
if (const auto *ND = dyn_cast_or_null<NamedDecl>(CE->getCalleeDecl())) {
return {ND};
}

return resolveExprToDecls(CE->getCallee());
}

std::vector<const NamedDecl *> HeuristicResolver::resolveUsingValueDecl(
const UnresolvedUsingValueDecl *UUVD) const {
std::vector<const NamedDecl *> HeuristicResolverImpl::resolveUsingValueDecl(
const UnresolvedUsingValueDecl *UUVD) {
return resolveDependentMember(UUVD->getQualifier()->getAsType(),
UUVD->getNameInfo().getName(), ValueFilter);
}

std::vector<const NamedDecl *> HeuristicResolver::resolveDependentNameType(
const DependentNameType *DNT) const {
std::vector<const NamedDecl *>
HeuristicResolverImpl::resolveDependentNameType(const DependentNameType *DNT) {
if (auto [_, inserted] = SeenDependentNameTypes.insert(DNT); !inserted)
return {};
return resolveDependentMember(
resolveNestedNameSpecifierToType(DNT->getQualifier()),
DNT->getIdentifier(), TypeFilter);
}

std::vector<const NamedDecl *>
HeuristicResolver::resolveTemplateSpecializationType(
const DependentTemplateSpecializationType *DTST) const {
HeuristicResolverImpl::resolveTemplateSpecializationType(
const DependentTemplateSpecializationType *DTST) {
return resolveDependentMember(
resolveNestedNameSpecifierToType(DTST->getQualifier()),
DTST->getIdentifier(), TemplateFilter);
}

std::vector<const NamedDecl *>
HeuristicResolver::resolveExprToDecls(const Expr *E) const {
HeuristicResolverImpl::resolveExprToDecls(const Expr *E) {
if (const auto *ME = dyn_cast<CXXDependentScopeMemberExpr>(E)) {
return resolveMemberExpr(ME);
}
Expand All @@ -236,16 +308,16 @@ HeuristicResolver::resolveExprToDecls(const Expr *E) const {
return {};
}

const Type *HeuristicResolver::resolveExprToType(const Expr *E) const {
const Type *HeuristicResolverImpl::resolveExprToType(const Expr *E) {
std::vector<const NamedDecl *> Decls = resolveExprToDecls(E);
if (!Decls.empty())
return resolveDeclsToType(Decls, Ctx);

return E->getType().getTypePtr();
}

const Type *HeuristicResolver::resolveNestedNameSpecifierToType(
const NestedNameSpecifier *NNS) const {
const Type *HeuristicResolverImpl::resolveNestedNameSpecifierToType(
const NestedNameSpecifier *NNS) {
if (!NNS)
return nullptr;

Expand All @@ -270,8 +342,6 @@ const Type *HeuristicResolver::resolveNestedNameSpecifierToType(
return nullptr;
}

namespace {

bool isOrdinaryMember(const NamedDecl *ND) {
return ND->isInIdentifierNamespace(Decl::IDNS_Ordinary | Decl::IDNS_Tag |
Decl::IDNS_Member);
Expand All @@ -287,21 +357,19 @@ bool findOrdinaryMember(const CXXRecordDecl *RD, CXXBasePath &Path,
return false;
}

} // namespace

bool HeuristicResolver::findOrdinaryMemberInDependentClasses(
bool HeuristicResolverImpl::findOrdinaryMemberInDependentClasses(
const CXXBaseSpecifier *Specifier, CXXBasePath &Path,
DeclarationName Name) const {
DeclarationName Name) {
CXXRecordDecl *RD =
resolveTypeToRecordDecl(Specifier->getType().getTypePtr());
if (!RD)
return false;
return findOrdinaryMember(RD, Path, Name);
}

std::vector<const NamedDecl *> HeuristicResolver::lookupDependentName(
std::vector<const NamedDecl *> HeuristicResolverImpl::lookupDependentName(
CXXRecordDecl *RD, DeclarationName Name,
llvm::function_ref<bool(const NamedDecl *ND)> Filter) const {
llvm::function_ref<bool(const NamedDecl *ND)> Filter) {
std::vector<const NamedDecl *> Results;

// Lookup in the class.
Expand Down Expand Up @@ -332,9 +400,9 @@ std::vector<const NamedDecl *> HeuristicResolver::lookupDependentName(
return Results;
}

std::vector<const NamedDecl *> HeuristicResolver::resolveDependentMember(
std::vector<const NamedDecl *> HeuristicResolverImpl::resolveDependentMember(
const Type *T, DeclarationName Name,
llvm::function_ref<bool(const NamedDecl *ND)> Filter) const {
llvm::function_ref<bool(const NamedDecl *ND)> Filter) {
if (!T)
return {};
if (auto *ET = T->getAs<EnumType>()) {
Expand All @@ -349,6 +417,44 @@ std::vector<const NamedDecl *> HeuristicResolver::resolveDependentMember(
}
return {};
}
} // namespace

std::vector<const NamedDecl *> HeuristicResolver::resolveMemberExpr(
const CXXDependentScopeMemberExpr *ME) const {
return HeuristicResolverImpl(Ctx).resolveMemberExpr(ME);
}
std::vector<const NamedDecl *> HeuristicResolver::resolveDeclRefExpr(
const DependentScopeDeclRefExpr *RE) const {
return HeuristicResolverImpl(Ctx).resolveDeclRefExpr(RE);
}
std::vector<const NamedDecl *>
HeuristicResolver::resolveTypeOfCallExpr(const CallExpr *CE) const {
return HeuristicResolverImpl(Ctx).resolveTypeOfCallExpr(CE);
}
std::vector<const NamedDecl *>
HeuristicResolver::resolveCalleeOfCallExpr(const CallExpr *CE) const {
return HeuristicResolverImpl(Ctx).resolveCalleeOfCallExpr(CE);
}
std::vector<const NamedDecl *> HeuristicResolver::resolveUsingValueDecl(
const UnresolvedUsingValueDecl *UUVD) const {
return HeuristicResolverImpl(Ctx).resolveUsingValueDecl(UUVD);
}
std::vector<const NamedDecl *> HeuristicResolver::resolveDependentNameType(
const DependentNameType *DNT) const {
return HeuristicResolverImpl(Ctx).resolveDependentNameType(DNT);
}
std::vector<const NamedDecl *>
HeuristicResolver::resolveTemplateSpecializationType(
const DependentTemplateSpecializationType *DTST) const {
return HeuristicResolverImpl(Ctx).resolveTemplateSpecializationType(DTST);
}
const Type *HeuristicResolver::resolveNestedNameSpecifierToType(
const NestedNameSpecifier *NNS) const {
return HeuristicResolverImpl(Ctx).resolveNestedNameSpecifierToType(NNS);
}
const Type *HeuristicResolver::getPointeeType(const Type *T) const {
return HeuristicResolverImpl(Ctx).getPointeeType(T);
}

} // namespace clangd
} // namespace clang
37 changes: 0 additions & 37 deletions clang-tools-extra/clangd/HeuristicResolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,43 +77,6 @@ class HeuristicResolver {

private:
ASTContext &Ctx;

// Given a tag-decl type and a member name, heuristically resolve the
// name to one or more declarations.
// The current heuristic is simply to look up the name in the primary
// template. This is a heuristic because the template could potentially
// have specializations that declare different members.
// Multiple declarations could be returned if the name is overloaded
// (e.g. an overloaded method in the primary template).
// This heuristic will give the desired answer in many cases, e.g.
// for a call to vector<T>::size().
std::vector<const NamedDecl *> resolveDependentMember(
const Type *T, DeclarationName Name,
llvm::function_ref<bool(const NamedDecl *ND)> Filter) const;

// Try to heuristically resolve the type of a possibly-dependent expression
// `E`.
const Type *resolveExprToType(const Expr *E) const;
std::vector<const NamedDecl *> resolveExprToDecls(const Expr *E) const;

// Helper function for HeuristicResolver::resolveDependentMember()
// which takes a possibly-dependent type `T` and heuristically
// resolves it to a CXXRecordDecl in which we can try name lookup.
CXXRecordDecl *resolveTypeToRecordDecl(const Type *T) const;

// This is a reimplementation of CXXRecordDecl::lookupDependentName()
// so that the implementation can call into other HeuristicResolver helpers.
// FIXME: Once HeuristicResolver is upstreamed to the clang libraries
// (https://github.com/clangd/clangd/discussions/1662),
// CXXRecordDecl::lookupDepenedentName() can be removed, and its call sites
// can be modified to benefit from the more comprehensive heuristics offered
// by HeuristicResolver instead.
std::vector<const NamedDecl *> lookupDependentName(
CXXRecordDecl *RD, DeclarationName Name,
llvm::function_ref<bool(const NamedDecl *ND)> Filter) const;
bool findOrdinaryMemberInDependentClasses(const CXXBaseSpecifier *Specifier,
CXXBasePath &Path,
DeclarationName Name) const;
};

} // namespace clangd
Expand Down
170 changes: 4 additions & 166 deletions clang-tools-extra/clangd/InlayHints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//
//===----------------------------------------------------------------------===//
#include "InlayHints.h"
#include "../clang-tidy/utils/DesignatedInitializers.h"
#include "AST.h"
#include "Config.h"
#include "HeuristicResolver.h"
Expand All @@ -24,7 +25,6 @@
#include "clang/Basic/OperatorKinds.h"
#include "clang/Basic/SourceManager.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
Expand All @@ -42,169 +42,6 @@ namespace {
// For now, inlay hints are always anchored at the left or right of their range.
enum class HintSide { Left, Right };

// Helper class to iterate over the designator names of an aggregate type.
//
// For an array type, yields [0], [1], [2]...
// For aggregate classes, yields null for each base, then .field1, .field2, ...
class AggregateDesignatorNames {
public:
AggregateDesignatorNames(QualType T) {
if (!T.isNull()) {
T = T.getCanonicalType();
if (T->isArrayType()) {
IsArray = true;
Valid = true;
return;
}
if (const RecordDecl *RD = T->getAsRecordDecl()) {
Valid = true;
FieldsIt = RD->field_begin();
FieldsEnd = RD->field_end();
if (const auto *CRD = llvm::dyn_cast<CXXRecordDecl>(RD)) {
BasesIt = CRD->bases_begin();
BasesEnd = CRD->bases_end();
Valid = CRD->isAggregate();
}
OneField = Valid && BasesIt == BasesEnd && FieldsIt != FieldsEnd &&
std::next(FieldsIt) == FieldsEnd;
}
}
}
// Returns false if the type was not an aggregate.
operator bool() { return Valid; }
// Advance to the next element in the aggregate.
void next() {
if (IsArray)
++Index;
else if (BasesIt != BasesEnd)
++BasesIt;
else if (FieldsIt != FieldsEnd)
++FieldsIt;
}
// Print the designator to Out.
// Returns false if we could not produce a designator for this element.
bool append(std::string &Out, bool ForSubobject) {
if (IsArray) {
Out.push_back('[');
Out.append(std::to_string(Index));
Out.push_back(']');
return true;
}
if (BasesIt != BasesEnd)
return false; // Bases can't be designated. Should we make one up?
if (FieldsIt != FieldsEnd) {
llvm::StringRef FieldName;
if (const IdentifierInfo *II = FieldsIt->getIdentifier())
FieldName = II->getName();

// For certain objects, their subobjects may be named directly.
if (ForSubobject &&
(FieldsIt->isAnonymousStructOrUnion() ||
// std::array<int,3> x = {1,2,3}. Designators not strictly valid!
(OneField && isReservedName(FieldName))))
return true;

if (!FieldName.empty() && !isReservedName(FieldName)) {
Out.push_back('.');
Out.append(FieldName.begin(), FieldName.end());
return true;
}
return false;
}
return false;
}

private:
bool Valid = false;
bool IsArray = false;
bool OneField = false; // e.g. std::array { T __elements[N]; }
unsigned Index = 0;
CXXRecordDecl::base_class_const_iterator BasesIt;
CXXRecordDecl::base_class_const_iterator BasesEnd;
RecordDecl::field_iterator FieldsIt;
RecordDecl::field_iterator FieldsEnd;
};

// Collect designator labels describing the elements of an init list.
//
// This function contributes the designators of some (sub)object, which is
// represented by the semantic InitListExpr Sem.
// This includes any nested subobjects, but *only* if they are part of the same
// original syntactic init list (due to brace elision).
// In other words, it may descend into subobjects but not written init-lists.
//
// For example: struct Outer { Inner a,b; }; struct Inner { int x, y; }
// Outer o{{1, 2}, 3};
// This function will be called with Sem = { {1, 2}, {3, ImplicitValue} }
// It should generate designators '.a:' and '.b.x:'.
// '.a:' is produced directly without recursing into the written sublist.
// (The written sublist will have a separate collectDesignators() call later).
// Recursion with Prefix='.b' and Sem = {3, ImplicitValue} produces '.b.x:'.
void collectDesignators(const InitListExpr *Sem,
llvm::DenseMap<SourceLocation, std::string> &Out,
const llvm::DenseSet<SourceLocation> &NestedBraces,
std::string &Prefix) {
if (!Sem || Sem->isTransparent())
return;
assert(Sem->isSemanticForm());

// The elements of the semantic form all correspond to direct subobjects of
// the aggregate type. `Fields` iterates over these subobject names.
AggregateDesignatorNames Fields(Sem->getType());
if (!Fields)
return;
for (const Expr *Init : Sem->inits()) {
auto Next = llvm::make_scope_exit([&, Size(Prefix.size())] {
Fields.next(); // Always advance to the next subobject name.
Prefix.resize(Size); // Erase any designator we appended.
});
// Skip for a broken initializer or if it is a "hole" in a subobject that
// was not explicitly initialized.
if (!Init || llvm::isa<ImplicitValueInitExpr>(Init))
continue;

const auto *BraceElidedSubobject = llvm::dyn_cast<InitListExpr>(Init);
if (BraceElidedSubobject &&
NestedBraces.contains(BraceElidedSubobject->getLBraceLoc()))
BraceElidedSubobject = nullptr; // there were braces!

if (!Fields.append(Prefix, BraceElidedSubobject != nullptr))
continue; // no designator available for this subobject
if (BraceElidedSubobject) {
// If the braces were elided, this aggregate subobject is initialized
// inline in the same syntactic list.
// Descend into the semantic list describing the subobject.
// (NestedBraces are still correct, they're from the same syntactic list).
collectDesignators(BraceElidedSubobject, Out, NestedBraces, Prefix);
continue;
}
Out.try_emplace(Init->getBeginLoc(), Prefix);
}
}

// Get designators describing the elements of a (syntactic) init list.
// This does not produce designators for any explicitly-written nested lists.
llvm::DenseMap<SourceLocation, std::string>
getDesignators(const InitListExpr *Syn) {
assert(Syn->isSyntacticForm());

// collectDesignators needs to know which InitListExprs in the semantic tree
// were actually written, but InitListExpr::isExplicit() lies.
// Instead, record where braces of sub-init-lists occur in the syntactic form.
llvm::DenseSet<SourceLocation> NestedBraces;
for (const Expr *Init : Syn->inits())
if (auto *Nested = llvm::dyn_cast<InitListExpr>(Init))
NestedBraces.insert(Nested->getLBraceLoc());

// Traverse the semantic form to find the designators.
// We use their SourceLocation to correlate with the syntactic form later.
llvm::DenseMap<SourceLocation, std::string> Designators;
std::string EmptyPrefix;
collectDesignators(Syn->isSemanticForm() ? Syn : Syn->getSemanticForm(),
Designators, NestedBraces, EmptyPrefix);
return Designators;
}

void stripLeadingUnderscores(StringRef &Name) { Name = Name.ltrim('_'); }

// getDeclForType() returns the decl responsible for Type's spelling.
Expand Down Expand Up @@ -847,14 +684,15 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
// This is the one we will ultimately attach designators to.
// It may have subobject initializers inlined without braces. The *semantic*
// form of the init-list has nested init-lists for these.
// getDesignators will look at the semantic form to determine the labels.
// getUnwrittenDesignators will look at the semantic form to determine the
// labels.
assert(Syn->isSyntacticForm() && "RAV should not visit implicit code!");
if (!Cfg.InlayHints.Designators)
return true;
if (Syn->isIdiomaticZeroInitializer(AST.getLangOpts()))
return true;
llvm::DenseMap<SourceLocation, std::string> Designators =
getDesignators(Syn);
tidy::utils::getUnwrittenDesignators(Syn);
for (const Expr *Init : Syn->inits()) {
if (llvm::isa<DesignatedInitExpr>(Init))
continue;
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/clangd/tool/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ clang_target_link_libraries(clangdMain
target_link_libraries(clangdMain
PRIVATE
clangTidy
clangTidyUtils

clangDaemon
clangdRemoteIndex
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/clangd/unittests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ target_link_libraries(ClangdTests
clangIncludeCleaner
clangTesting
clangTidy
clangTidyUtils
clangdSupport
)

Expand Down
27 changes: 27 additions & 0 deletions clang-tools-extra/clangd/unittests/FindTargetTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,33 @@ TEST_F(TargetDeclTest, DependentTypes) {
)cpp";
EXPECT_DECLS("DependentTemplateSpecializationTypeLoc",
"template <typename> struct B");

// Dependent name with recursive definition. We don't expect a
// result, but we shouldn't get into a stack overflow either.
Code = R"cpp(
template <int N>
struct waldo {
typedef typename waldo<N - 1>::type::[[next]] type;
};
)cpp";
EXPECT_DECLS("DependentNameTypeLoc");

// Similar to above but using mutually recursive templates.
Code = R"cpp(
template <int N>
struct odd;
template <int N>
struct even {
using type = typename odd<N - 1>::type::next;
};
template <int N>
struct odd {
using type = typename even<N - 1>::type::[[next]];
};
)cpp";
EXPECT_DECLS("DependentNameTypeLoc");
}

TEST_F(TargetDeclTest, TypedefCascade) {
Expand Down
24 changes: 24 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,18 @@ Improvements to clang-tidy
New checks
^^^^^^^^^^

- New :doc:`bugprone-crtp-constructor-accessibility
<clang-tidy/checks/bugprone/crtp-constructor-accessibility>` check.

Detects error-prone Curiously Recurring Template Pattern usage, when the CRTP
can be constructed outside itself and the derived class.

- New :doc:`modernize-use-designated-initializers
<clang-tidy/checks/modernize/use-designated-initializers>` check.

Finds initializer lists for aggregate types that could be
written as designated initializers instead.

- New :doc:`readability-use-std-min-max
<clang-tidy/checks/readability/use-std-min-max>` check.

Expand Down Expand Up @@ -134,6 +146,14 @@ Changes in existing checks
<clang-tidy/checks/bugprone/unused-local-non-trivial-variable>` check by
ignoring local variable with ``[maybe_unused]`` attribute.

- Improved :doc:`bugprone-unused-return-value
<clang-tidy/checks/bugprone/unused-return-value>` check by updating the
parameter `CheckedFunctions` to support regexp.

- Improved :doc:`bugprone-use-after-move
<clang-tidy/checks/bugprone/use-after-move>` check to also handle
calls to ``std::forward``.

- Improved :doc:`cppcoreguidelines-missing-std-forward
<clang-tidy/checks/cppcoreguidelines/missing-std-forward>` check by no longer
giving false positives for deleted functions.
Expand All @@ -151,6 +171,10 @@ Changes in existing checks
<clang-tidy/checks/google/build-namespaces>` check by replacing the local
option `HeaderFileExtensions` by the global option of the same name.

- Improved :doc:`google-explicit-constructor
<clang-tidy/checks/google/explicit-constructor>` check to better handle
``C++-20`` `explicit(bool)`.

- Improved :doc:`google-global-names-in-headers
<clang-tidy/checks/google/global-names-in-headers>` check by replacing the local
option `HeaderFileExtensions` by the global option of the same name.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
.. title:: clang-tidy - bugprone-crtp-constructor-accessibility

bugprone-crtp-constructor-accessibility
=======================================

Detects error-prone Curiously Recurring Template Pattern usage, when the CRTP
can be constructed outside itself and the derived class.

The CRTP is an idiom, in which a class derives from a template class, where
itself is the template argument. It should be ensured that if a class is
intended to be a base class in this idiom, it can only be instantiated if
the derived class is it's template argument.

Example:

.. code-block:: c++

template <typename T> class CRTP {
private:
CRTP() = default;
friend T;
};

class Derived : CRTP<Derived> {};

Below can be seen some common mistakes that will allow the breaking of the
idiom.

If the constructor of a class intended to be used in a CRTP is public, then
it allows users to construct that class on its own.

Example:

.. code-block:: c++

template <typename T> class CRTP {
public:
CRTP() = default;
};

class Good : CRTP<Good> {};
Good GoodInstance;

CRTP<int> BadInstance;

If the constructor is protected, the possibility of an accidental instantiation
is prevented, however it can fade an error, when a different class is used as
the template parameter instead of the derived one.

Example:

.. code-block:: c++

template <typename T> class CRTP {
protected:
CRTP() = default;
};

class Good : CRTP<Good> {};
Good GoodInstance;

class Bad : CRTP<Good> {};
Bad BadInstance;

To ensure that no accidental instantiation happens, the best practice is to
make the constructor private and declare the derived class as friend. Note
that as a tradeoff, this also gives the derived class access to every other
private members of the CRTP.

Example:

.. code-block:: c++

template <typename T> class CRTP {
CRTP() = default;
friend T;
};

class Good : CRTP<Good> {};
Good GoodInstance;

class Bad : CRTP<Good> {};
Bad CompileTimeError;

CRTP<int> AlsoCompileTimeError;

Limitations:

* The check is not supported below C++11

* The check does not handle when the derived class is passed as a variadic
template argument

* Accessible functions that can construct the CRTP, like factory functions
are not checked

The check also suggests a fix-its in some cases.

Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ Options

.. option:: CheckedFunctions

Semicolon-separated list of functions to check. The function is checked if
the name and scope matches, with any arguments.
Semicolon-separated list of functions to check.
This parameter supports regexp. The function is checked if the name
and scope matches, with any arguments.
By default the following functions are checked:
``std::async, std::launder, std::remove, std::remove_if, std::unique,
std::unique_ptr::release, std::basic_string::empty, std::vector::empty,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,18 @@ When analyzing the order in which moves, uses and reinitializations happen (see
section `Unsequenced moves, uses, and reinitializations`_), the move is assumed
to occur in whichever function the result of the ``std::move`` is passed to.

The check also handles perfect-forwarding with ``std::forward`` so the
following code will also trigger a use-after-move warning.

.. code-block:: c++

void consume(int);

void f(int&& i) {
consume(std::forward<int>(i));
consume(std::forward<int>(i)); // use-after-move
}

Use
---

Expand Down
2 changes: 2 additions & 0 deletions clang-tools-extra/docs/clang-tidy/checks/list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ Clang-Tidy Checks
:doc:`bugprone-chained-comparison <bugprone/chained-comparison>`,
:doc:`bugprone-compare-pointer-to-member-virtual-function <bugprone/compare-pointer-to-member-virtual-function>`,
:doc:`bugprone-copy-constructor-init <bugprone/copy-constructor-init>`, "Yes"
:doc:`bugprone-crtp-constructor-accessibility <bugprone/crtp-constructor-accessibility>`, "Yes"
:doc:`bugprone-dangling-handle <bugprone/dangling-handle>`,
:doc:`bugprone-dynamic-static-initializers <bugprone/dynamic-static-initializers>`,
:doc:`bugprone-easily-swappable-parameters <bugprone/easily-swappable-parameters>`,
Expand Down Expand Up @@ -287,6 +288,7 @@ Clang-Tidy Checks
:doc:`modernize-use-bool-literals <modernize/use-bool-literals>`, "Yes"
:doc:`modernize-use-constraints <modernize/use-constraints>`, "Yes"
:doc:`modernize-use-default-member-init <modernize/use-default-member-init>`, "Yes"
:doc:`modernize-use-designated-initializers <modernize/use-designated-initializers>`, "Yes"
:doc:`modernize-use-emplace <modernize/use-emplace>`, "Yes"
:doc:`modernize-use-equals-default <modernize/use-equals-default>`, "Yes"
:doc:`modernize-use-equals-delete <modernize/use-equals-delete>`, "Yes"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
.. title:: clang-tidy - modernize-use-designated-initializers

modernize-use-designated-initializers
=====================================

Finds initializer lists for aggregate types which could be written as designated
initializers instead.

With plain initializer lists, it is very easy to introduce bugs when adding new
fields in the middle of a struct or class type. The same confusion might arise
when changing the order of fields.

C++20 supports the designated initializer syntax for aggregate types. By
applying it, we can always be sure that aggregates are constructed correctly,
because every variable being initialized is referenced by its name.

Example:

.. code-block::
struct S { int i, j; };
is an aggregate type that should be initialized as

.. code-block::
S s{.i = 1, .j = 2};
instead of

.. code-block::
S s{1, 2};
which could easily become an issue when ``i`` and ``j`` are swapped in the
declaration of ``S``.

Even when compiling in a language version older than C++20, depending on your
compiler, designated initializers are potentially supported. Therefore, the
check is not restricted to C++20 and newer versions. Check out the options
``-Wc99-designator`` to get support for mixed designators in initializer list in
C and ``-Wc++20-designator`` for support of designated initializers in older C++
language modes.

Options
-------

.. option:: IgnoreMacros

The value `false` specifies that components of initializer lists expanded from
macros are not checked. The default value is `true`.

.. option:: IgnoreSingleElementAggregates

The value `false` specifies that even initializers for aggregate types with
only a single element should be checked. The default value is `true`.

.. option:: RestrictToPODTypes

The value `true` specifies that only Plain Old Data (POD) types shall be
checked. This makes the check applicable to even older C++ standards. The
default value is `false`.
5 changes: 5 additions & 0 deletions clang-tools-extra/include-cleaner/lib/WalkAST.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,11 @@ class ASTWalker : public RecursiveASTVisitor<ASTWalker> {
// Mark declaration from definition as it needs type-checking.
if (FD->isThisDeclarationADefinition())
report(FD->getLocation(), FD);
// Explicit specializaiton/instantiations of a function template requires
// primary template.
if (clang::isTemplateExplicitInstantiationOrSpecialization(
FD->getTemplateSpecializationKind()))
report(FD->getLocation(), FD->getPrimaryTemplate());
return true;
}
bool VisitVarDecl(VarDecl *VD) {
Expand Down
10 changes: 4 additions & 6 deletions clang-tools-extra/include-cleaner/unittests/WalkASTTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,13 +229,9 @@ TEST(WalkAST, FunctionTemplates) {
EXPECT_THAT(testWalk("template<typename T> void foo(T) {}",
"template void ^foo<int>(int);"),
ElementsAre());
// FIXME: Report specialized template as used from explicit specializations.
EXPECT_THAT(testWalk("template<typename T> void foo(T);",
EXPECT_THAT(testWalk("template<typename T> void $explicit^foo(T);",
"template<> void ^foo<int>(int);"),
ElementsAre());
EXPECT_THAT(testWalk("template<typename T> void foo(T) {}",
"template<typename T> void ^foo(T*) {}"),
ElementsAre());
ElementsAre(Decl::FunctionTemplate));

// Implicit instantiations references most relevant template.
EXPECT_THAT(testWalk(R"cpp(
Expand Down Expand Up @@ -510,6 +506,8 @@ TEST(WalkAST, Functions) {
// Definition uses declaration, not the other way around.
testWalk("void $explicit^foo();", "void ^foo() {}");
testWalk("void foo() {}", "void ^foo();");
testWalk("template <typename> void $explicit^foo();",
"template <typename> void ^foo() {}");

// Unresolved calls marks all the overloads.
testWalk("void $ambiguous^foo(int); void $ambiguous^foo(char);",
Expand Down
Loading