556 changes: 448 additions & 108 deletions bolt/lib/Rewrite/LinuxKernelRewriter.cpp

Large diffs are not rendered by default.

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
3 changes: 1 addition & 2 deletions bolt/test/X86/gotpcrelx.s
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
## kinds of handling of the relocation by the linker (no relaxation, pic, and
## non-pic).

# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-linux \
# RUN: -relax-relocations %s -o %t.o
# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-linux %s -o %t.o
# RUN: ld.lld %t.o -o %t.exe -q
# RUN: ld.lld %t.o -o %t.pie.exe -q -pie
# RUN: ld.lld %t.o -o %t.no-relax.exe -q --no-relax
Expand Down
97 changes: 97 additions & 0 deletions bolt/test/X86/linux-alt-instruction.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# REQUIRES: system-linux

## Check that BOLT correctly parses the Linux kernel .altinstructions section
## and annotates alternative instructions.

# 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 -o %t.out \
# RUN: --alt-inst-feature-size=2 | FileCheck %s

## Older kernels used to have padlen field in alt_instr. Check compatibility.

# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-unknown --defsym PADLEN=1 \
# RUN: %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 --alt-inst-has-padlen \
# RUN: -o %t.out | FileCheck %s

## Check with a larger size of "feature" field in alt_instr.

# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-unknown \
# RUN: --defsym FEATURE_SIZE_4=1 %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 \
# RUN: --alt-inst-feature-size=4 -o %t.out | FileCheck %s

## Check that out-of-bounds read is handled properly.

# RUN: not llvm-bolt %t.exe --print-normalized --keep-nops \
# RUN: --alt-inst-feature-size=2 -o %t.out

# CHECK: BOLT-INFO: Linux kernel binary detected
# CHECK: BOLT-INFO: parsed 2 alternative instruction entries

.text
.globl _start
.type _start, %function
_start:
# CHECK: Binary Function "_start"
.L0:
rdtsc
# CHECK: rdtsc
# CHECK-SAME: AltInst: 1
# CHECK-SAME: AltInst2: 2
nop
# CHECK-NEXT: nop
# CHECK-SAME: AltInst: 1
# CHECK-SAME: AltInst2: 2
nop
nop
.L1:
ret
.size _start, .-_start

.section .altinstr_replacement,"ax",@progbits
.A0:
lfence
rdtsc
.A1:
rdtscp
.Ae:

## Alternative instruction info.
.section .altinstructions,"a",@progbits

.long .L0 - . # org instruction
.long .A0 - . # alt instruction
.ifdef FEATURE_SIZE_4
.long 0x72 # feature flags
.else
.word 0x72 # feature flags
.endif
.byte .L1 - .L0 # org size
.byte .A1 - .A0 # alt size
.ifdef PADLEN
.byte 0
.endif

.long .L0 - . # org instruction
.long .A1 - . # alt instruction
.ifdef FEATURE_SIZE_4
.long 0x3b # feature flags
.else
.word 0x3b # feature flags
.endif
.byte .L1 - .L0 # org size
.byte .Ae - .A1 # alt size
.ifdef PADLEN
.byte 0
.endif

## Fake Linux Kernel sections.
.section __ksymtab,"a",@progbits
.section __ksymtab_gpl,"a",@progbits
46 changes: 46 additions & 0 deletions bolt/test/X86/linux-bug-table.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# REQUIRES: system-linux

## Check that BOLT correctly parses the Linux kernel __bug_table 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

## Verify bug entry bindings to instructions.

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

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

.text
.globl _start
.type _start, %function
_start:
# CHECK: Binary Function "_start"
nop
.L0:
ud2
# CHECK: ud2
# CHECK-SAME: BugEntry: 1
nop
.L1:
ud2
# CHECK: ud2
# CHECK-SAME: BugEntry: 2
ret
.size _start, .-_start


## Bug table.
.section __bug_table,"a",@progbits
1:
.long .L0 - . # instruction
.org 1b + 12
2:
.long .L1 - . # instruction
.org 2b + 12

## Fake Linux Kernel sections.
.section __ksymtab,"a",@progbits
.section __ksymtab_gpl,"a",@progbits
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
54 changes: 54 additions & 0 deletions bolt/test/X86/linux-parainstructions.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# REQUIRES: system-linux

## Check that BOLT correctly parses the Linux kernel .parainstructions 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

## Verify paravirtual bindings to instructions.

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

# CHECK: BOLT-INFO: Linux kernel binary detected
# CHECK: BOLT-INFO: parsed 2 paravirtual patch sites

.rodata
fptr:
.quad 0

.text
.globl _start
.type _start, %function
_start:
# CHECK: Binary Function "_start"
nop
.L1:
call *fptr(%rip)
# CHECK: call
# CHECK-SAME: ParaSite: 1
nop
.L2:
call *fptr(%rip)
# CHECK: call
# CHECK-SAME: ParaSite: 2
ret
.size _start, .-_start


## Paravirtual patch sites.
.section .parainstructions,"a",@progbits

.balign 8
.quad .L1 # instruction
.byte 1 # type
.byte 7 # length

.balign 8
.quad .L2 # instruction
.byte 1 # type
.byte 7 # length

## Fake Linux Kernel sections.
.section __ksymtab,"a",@progbits
.section __ksymtab_gpl,"a",@progbits
2 changes: 1 addition & 1 deletion bolt/test/X86/pt_gnu_relro.s
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Check that BOLT recognizes PT_GNU_RELRO segment and marks respective sections
# accordingly.

# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-linux %s -o %t.o -relax-relocations
# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-linux %s -o %t.o
# RUN: ld.lld %t.o -o %t.exe -q --no-relax
# RUN: llvm-readelf -We %t.exe | FileCheck --check-prefix=READELF %s
# Unfortunately there's no direct way to extract a segment to section mapping
Expand Down
2 changes: 1 addition & 1 deletion bolt/tools/merge-fdata/merge-fdata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ void mergeLegacyProfiles(const SmallVectorImpl<std::string> &Filenames) {
// least 4 tasks.
ThreadPoolStrategy S = optimal_concurrency(
std::max(Filenames.size() / 4, static_cast<size_t>(1)));
ThreadPool Pool(S);
DefaultThreadPool Pool(S);
DenseMap<llvm::thread::id, ProfileTy> ParsedProfiles(
Pool.getMaxConcurrency());
for (const auto &Filename : Filenames)
Expand Down
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
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ Example usage for a project using a compile commands database:
Error = false;
llvm::sys::Mutex IndexMutex;
// ExecutorConcurrency is a flag exposed by AllTUsExecution.h
llvm::ThreadPool Pool(llvm::hardware_concurrency(ExecutorConcurrency));
llvm::DefaultThreadPool Pool(llvm::hardware_concurrency(ExecutorConcurrency));
for (auto &Group : USRToBitcode) {
Pool.async([&]() {
std::vector<std::unique_ptr<doc::Info>> Infos;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ bool Merge(llvm::StringRef MergeDir, llvm::StringRef OutputFile) {

// Load all symbol files in MergeDir.
{
llvm::ThreadPool Pool;
llvm::DefaultThreadPool Pool;
for (llvm::sys::fs::directory_iterator Dir(MergeDir, EC), DirEnd;
Dir != DirEnd && !EC; Dir.increment(EC)) {
// Parse YAML files in parallel.
Expand Down
20 changes: 15 additions & 5 deletions clang-tools-extra/clang-tidy/bugprone/AssertSideEffectCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,26 @@ AST_MATCHER_P2(Expr, hasSideEffect, bool, CheckFunctionCalls,
}

if (const auto *CExpr = dyn_cast<CallExpr>(E)) {
bool Result = CheckFunctionCalls;
if (!CheckFunctionCalls)
return false;
if (const auto *FuncDecl = CExpr->getDirectCallee()) {
if (FuncDecl->getDeclName().isIdentifier() &&
IgnoredFunctionsMatcher.matches(*FuncDecl, Finder,
Builder)) // exceptions come here
Result = false;
else if (const auto *MethodDecl = dyn_cast<CXXMethodDecl>(FuncDecl))
Result &= !MethodDecl->isConst();
return false;
for (size_t I = 0; I < FuncDecl->getNumParams(); I++) {
const ParmVarDecl *P = FuncDecl->getParamDecl(I);
const Expr *ArgExpr =
I < CExpr->getNumArgs() ? CExpr->getArg(I) : nullptr;
const QualType PT = P->getType().getCanonicalType();
if (ArgExpr && !ArgExpr->isXValue() && PT->isReferenceType() &&
!PT.getNonReferenceType().isConstQualified())
return true;
}
if (const auto *MethodDecl = dyn_cast<CXXMethodDecl>(FuncDecl))
return !MethodDecl->isConst();
}
return Result;
return true;
}

return isa<CXXNewExpr>(E) || isa<CXXDeleteExpr>(E) || isa<CXXThrowExpr>(E);
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
210 changes: 105 additions & 105 deletions clang-tools-extra/clang-tidy/bugprone/UnusedReturnValueCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,136 +33,136 @@ 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;"
"::std::errc;"
"::std::expected;"
Options.get("CheckedReturnTypes", "::std::error_code$;"
"::std::error_condition$;"
"::std::errc$;"
"::std::expected$;"
"::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
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,12 @@ void MissingStdForwardCheck::registerMatchers(MatchFinder *Finder) {

auto ForwardCallMatcher = callExpr(
callExpr().bind("call"), argumentCountIs(1),
hasArgument(
0, declRefExpr(to(
varDecl(optionally(equalsBoundNode("param"))).bind("var")))),
forCallable(anyOf(equalsBoundNode("func"), CapturedInLambda)),
hasArgument(0, declRefExpr(to(varDecl().bind("var")))),
forCallable(
anyOf(allOf(equalsBoundNode("func"),
functionDecl(hasAnyParameter(parmVarDecl(allOf(
equalsBoundNode("param"), equalsBoundNode("var")))))),
CapturedInLambda)),
callee(unresolvedLookupExpr(hasAnyDeclaration(
namedDecl(hasUnderlyingDecl(hasName("::std::forward")))))),

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
Original file line number Diff line number Diff line change
Expand Up @@ -888,8 +888,14 @@ bool IdentifierNamingCheck::matchesStyle(
return false;
if (IdentifierNamingCheck::HungarianPrefixType::HPT_Off != Style.HPType) {
std::string HNPrefix = HungarianNotation.getPrefix(Decl, HNOption);
if (!Name.consume_front(HNPrefix))
return false;
if (!HNPrefix.empty()) {
if (!Name.consume_front(HNPrefix))
return false;
if (Style.HPType ==
IdentifierNamingCheck::HungarianPrefixType::HPT_LowerCase &&
!Name.consume_front("_"))
return false;
}
}

// Ensure the name doesn't have any extra underscores beyond those specified
Expand Down
9 changes: 5 additions & 4 deletions clang-tools-extra/clang-tidy/utils/DeclRefExprUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,15 +155,16 @@ AST_MATCHER_P(DeclRefExpr, doesNotMutateObject, int, Indirections) {
if (const auto *const Member = dyn_cast<MemberExpr>(P)) {
if (const auto *const Method =
dyn_cast<CXXMethodDecl>(Member->getMemberDecl())) {
if (!Method->isConst()) {
// The method can mutate our variable.
return false;
if (Method->isConst() || Method->isStatic()) {
// The method call cannot mutate our variable.
continue;
}
continue;
return false;
}
Stack.emplace_back(Member, 0);
continue;
}

if (const auto *const Op = dyn_cast<UnaryOperator>(P)) {
switch (Op->getOpcode()) {
case UO_AddrOf:
Expand Down
9 changes: 2 additions & 7 deletions clang-tools-extra/clangd/ClangdServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -551,15 +551,10 @@ void ClangdServer::formatOnType(PathRef File, Position Pos,
auto Action = [File = File.str(), Code = std::move(*Code),
TriggerText = TriggerText.str(), CursorPos = *CursorPos,
CB = std::move(CB), this]() mutable {
auto Style = format::getStyle(format::DefaultFormatStyle, File,
format::DefaultFallbackStyle, Code,
TFS.view(/*CWD=*/std::nullopt).get());
if (!Style)
return CB(Style.takeError());

auto Style = getFormatStyleForFile(File, Code, TFS);
std::vector<TextEdit> Result;
for (const tooling::Replacement &R :
formatIncremental(Code, CursorPos, TriggerText, *Style))
formatIncremental(Code, CursorPos, TriggerText, Style))
Result.push_back(replacementToEdit(Code, R));
return CB(Result);
};
Expand Down
4 changes: 2 additions & 2 deletions clang-tools-extra/clangd/FindSymbols.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,8 @@ std::string getSymbolDetail(ASTContext &Ctx, const NamedDecl &ND) {
std::optional<DocumentSymbol> declToSym(ASTContext &Ctx, const NamedDecl &ND) {
auto &SM = Ctx.getSourceManager();

SourceLocation BeginLoc = SM.getFileLoc(ND.getBeginLoc());
SourceLocation EndLoc = SM.getFileLoc(ND.getEndLoc());
SourceLocation BeginLoc = ND.getBeginLoc();
SourceLocation EndLoc = ND.getEndLoc();
const auto SymbolRange =
toHalfOpenFileRange(SM, Ctx.getLangOpts(), {BeginLoc, EndLoc});
if (!SymbolRange)
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
15 changes: 5 additions & 10 deletions clang-tools-extra/clangd/IncludeCleaner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,21 +111,15 @@ bool mayConsiderUnused(const Inclusion &Inc, ParsedAST &AST,

std::vector<Diag> generateMissingIncludeDiagnostics(
ParsedAST &AST, llvm::ArrayRef<MissingIncludeDiagInfo> MissingIncludes,
llvm::StringRef Code, HeaderFilter IgnoreHeaders) {
llvm::StringRef Code, HeaderFilter IgnoreHeaders, const ThreadsafeFS &TFS) {
std::vector<Diag> Result;
const SourceManager &SM = AST.getSourceManager();
const FileEntry *MainFile = SM.getFileEntryForID(SM.getMainFileID());

auto FileStyle = format::getStyle(
format::DefaultFormatStyle, AST.tuPath(), format::DefaultFallbackStyle,
Code, &SM.getFileManager().getVirtualFileSystem());
if (!FileStyle) {
elog("Couldn't infer style", FileStyle.takeError());
FileStyle = format::getLLVMStyle();
}
auto FileStyle = getFormatStyleForFile(AST.tuPath(), Code, TFS);

tooling::HeaderIncludes HeaderIncludes(AST.tuPath(), Code,
FileStyle->IncludeStyle);
FileStyle.IncludeStyle);
for (const auto &SymbolWithMissingInclude : MissingIncludes) {
llvm::StringRef ResolvedPath =
SymbolWithMissingInclude.Providers.front().resolvedPath();
Expand Down Expand Up @@ -459,14 +453,15 @@ bool isPreferredProvider(const Inclusion &Inc,
std::vector<Diag>
issueIncludeCleanerDiagnostics(ParsedAST &AST, llvm::StringRef Code,
const IncludeCleanerFindings &Findings,
const ThreadsafeFS &TFS,
HeaderFilter IgnoreHeaders) {
trace::Span Tracer("IncludeCleaner::issueIncludeCleanerDiagnostics");
std::vector<Diag> UnusedIncludes = generateUnusedIncludeDiagnostics(
AST.tuPath(), Findings.UnusedIncludes, Code, IgnoreHeaders);
std::optional<Fix> RemoveAllUnused = removeAllUnusedIncludes(UnusedIncludes);

std::vector<Diag> MissingIncludeDiags = generateMissingIncludeDiagnostics(
AST, Findings.MissingIncludes, Code, IgnoreHeaders);
AST, Findings.MissingIncludes, Code, IgnoreHeaders, TFS);
std::optional<Fix> AddAllMissing = addAllMissingIncludes(MissingIncludeDiags);

std::optional<Fix> FixAll;
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/clangd/IncludeCleaner.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ using HeaderFilter = llvm::ArrayRef<std::function<bool(llvm::StringRef)>>;
std::vector<Diag>
issueIncludeCleanerDiagnostics(ParsedAST &AST, llvm::StringRef Code,
const IncludeCleanerFindings &Findings,
const ThreadsafeFS &TFS,
HeaderFilter IgnoreHeader = {});

/// Affects whether standard library includes should be considered for
Expand Down
7 changes: 4 additions & 3 deletions clang-tools-extra/clangd/ParsedAST.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,8 @@ void applyWarningOptions(llvm::ArrayRef<std::string> ExtraArgs,
}
}

std::vector<Diag> getIncludeCleanerDiags(ParsedAST &AST, llvm::StringRef Code) {
std::vector<Diag> getIncludeCleanerDiags(ParsedAST &AST, llvm::StringRef Code,
const ThreadsafeFS &TFS) {
auto &Cfg = Config::current();
if (Cfg.Diagnostics.SuppressAll)
return {};
Expand All @@ -377,7 +378,7 @@ std::vector<Diag> getIncludeCleanerDiags(ParsedAST &AST, llvm::StringRef Code) {
Findings.MissingIncludes.clear();
if (SuppressUnused)
Findings.UnusedIncludes.clear();
return issueIncludeCleanerDiagnostics(AST, Code, Findings,
return issueIncludeCleanerDiagnostics(AST, Code, Findings, TFS,
Cfg.Diagnostics.Includes.IgnoreHeader);
}

Expand Down Expand Up @@ -741,7 +742,7 @@ ParsedAST::build(llvm::StringRef Filename, const ParseInputs &Inputs,
std::move(Clang), std::move(Action), std::move(Tokens),
std::move(Macros), std::move(Marks), std::move(ParsedDecls),
std::move(Diags), std::move(Includes), std::move(PI));
llvm::move(getIncludeCleanerDiags(Result, Inputs.Contents),
llvm::move(getIncludeCleanerDiags(Result, Inputs.Contents, *Inputs.TFS),
std::back_inserter(Result.Diags));
return std::move(Result);
}
Expand Down
4 changes: 4 additions & 0 deletions clang-tools-extra/clangd/refactor/Rename.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1062,6 +1062,10 @@ llvm::Expected<RenameResult> rename(const RenameInputs &RInputs) {
return makeError(ReasonToReject::AmbiguousSymbol);

const auto &RenameDecl = **DeclsUnderCursor.begin();
static constexpr trace::Metric RenameTriggerCounter(
"rename_trigger_count", trace::Metric::Counter, "decl_kind");
RenameTriggerCounter.record(1, RenameDecl.getDeclKindName());

std::string Placeholder = getName(RenameDecl);
auto Invalid = checkName(RenameDecl, RInputs.NewName, Placeholder);
if (Invalid)
Expand Down
17 changes: 17 additions & 0 deletions clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1462,6 +1462,23 @@ TEST(SignatureHelpTest, FunctionPointers) {
typedef void (__stdcall *fn)(int x, int y);
fn foo;
int main() { foo(^); }
)cpp",
// Field of function pointer type
R"cpp(
struct S {
void (*foo)(int x, int y);
};
S s;
int main() { s.foo(^); }
)cpp",
// Field of function pointer typedef type
R"cpp(
typedef void (*fn)(int x, int y);
struct S {
fn foo;
};
S s;
int main() { s.foo(^); }
)cpp"};
for (auto Test : Tests)
EXPECT_THAT(signatures(Test).signatures,
Expand Down
9 changes: 8 additions & 1 deletion clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,9 @@ TEST(DocumentSymbols, RangeFromMacro) {
$fullDef[[FF3() {
int var = 42;
}]]
#define FF4(name) int name = 0
$FooRange[[FF4($FooSelectionRange[[foo]])]];
)");
TU.Code = Main.code().str();
EXPECT_THAT(
Expand All @@ -766,7 +769,11 @@ TEST(DocumentSymbols, RangeFromMacro) {
AllOf(withName("FF3"), withDetail("()"),
symRange(Main.range("fullDef")),
children(AllOf(withName("waldo"), withDetail("void ()"),
symRange(Main.range("fullDef")))))));
symRange(Main.range("fullDef"))))),
AllOf(
withName("FF4"), withDetail("(foo)"),
children(AllOf(withName("foo"), symRange(Main.range("FooRange")),
symNameRange(Main.range("FooSelectionRange")))))));
}

TEST(DocumentSymbols, FuncTemplates) {
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
14 changes: 7 additions & 7 deletions clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ TEST(IncludeCleaner, GenerateMissingHeaderDiags) {
auto Findings = computeIncludeCleanerFindings(AST);
Findings.UnusedIncludes.clear();
std::vector<clangd::Diag> Diags = issueIncludeCleanerDiagnostics(
AST, TU.Code, Findings,
AST, TU.Code, Findings, MockFS(),
{[](llvm::StringRef Header) { return Header.ends_with("buzz.h"); }});
EXPECT_THAT(
Diags,
Expand Down Expand Up @@ -505,8 +505,8 @@ TEST(IncludeCleaner, BatchFix) {
)cpp";
auto AST = TU.build();
EXPECT_THAT(
issueIncludeCleanerDiagnostics(AST, TU.Code,
computeIncludeCleanerFindings(AST)),
issueIncludeCleanerDiagnostics(
AST, TU.Code, computeIncludeCleanerFindings(AST), MockFS()),
UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""),
FixMessage("fix all includes")}),
withFix({FixMessage("remove #include directive"),
Expand All @@ -520,8 +520,8 @@ TEST(IncludeCleaner, BatchFix) {
)cpp";
AST = TU.build();
EXPECT_THAT(
issueIncludeCleanerDiagnostics(AST, TU.Code,
computeIncludeCleanerFindings(AST)),
issueIncludeCleanerDiagnostics(
AST, TU.Code, computeIncludeCleanerFindings(AST), MockFS()),
UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""),
FixMessage("fix all includes")}),
withFix({FixMessage("remove #include directive"),
Expand All @@ -539,8 +539,8 @@ TEST(IncludeCleaner, BatchFix) {
)cpp";
AST = TU.build();
EXPECT_THAT(
issueIncludeCleanerDiagnostics(AST, TU.Code,
computeIncludeCleanerFindings(AST)),
issueIncludeCleanerDiagnostics(
AST, TU.Code, computeIncludeCleanerFindings(AST), MockFS()),
UnorderedElementsAre(withFix({FixMessage("#include \"foo.h\""),
FixMessage("add all missing includes"),
FixMessage("fix all includes")}),
Expand Down
30 changes: 28 additions & 2 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ 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.

Expand All @@ -122,6 +128,10 @@ New check aliases
Changes in existing checks
^^^^^^^^^^^^^^^^^^^^^^^^^^

- Improved :doc:`bugprone-assert-side-effect
<clang-tidy/checks/bugprone/assert-side-effect>` check by detecting side
effect from calling a method with non-const reference parameters.

- Improved :doc:`bugprone-non-zero-enum-to-bool-conversion
<clang-tidy/checks/bugprone/non-zero-enum-to-bool-conversion>` check by
eliminating false positives resulting from direct usage of bitwise operators
Expand All @@ -140,9 +150,20 @@ 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 and avoiding false postive for
function with the same prefix as the default argument, e.g. ``std::unique_ptr``
and ``std::unique``.

- 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.
giving false positives for deleted functions and fix false negative when some
parameters are forwarded, but other aren't.

- Cleaned up :doc:`cppcoreguidelines-prefer-member-initializer
<clang-tidy/checks/cppcoreguidelines/prefer-member-initializer>`
Expand All @@ -157,6 +178,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 Expand Up @@ -210,7 +235,8 @@ Changes in existing checks

- Improved :doc:`readability-identifier-naming
<clang-tidy/checks/readability/identifier-naming>` check in `GetConfigPerFile`
mode by resolving symbolic links to header files.
mode by resolving symbolic links to header files. Fixed handling of Hungarian
Prefix when configured to `LowerCase`.

Removed checks
^^^^^^^^^^^^^^
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,25 +10,27 @@ 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,
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,
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, 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``
``::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$, ::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$, ::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$``

- ``std::async()``. Not using the return value makes the call synchronous.
- ``std::launder()``. Not using the return value usually means that the
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
1 change: 1 addition & 0 deletions clang-tools-extra/docs/clang-tidy/checks/list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ another if a mismatch is detected

Casing types include:

- ``lower_case``,
- ``UPPER_CASE``,
- ``camelBack``,
- ``CamelCase``,
- ``camel_Snake_Back``,
- ``Camel_Snake_Case``,
- ``aNy_CasE``,
- ``Leading_upper_snake_case``.
- ``lower_case``
- ``UPPER_CASE``
- ``camelBack``
- ``CamelCase``
- ``camel_Snake_Back``
- ``Camel_Snake_Case``
- ``aNy_CasE``
- ``Leading_upper_snake_case``

It also supports a fixed prefix and suffix that will be prepended or appended
to the identifiers, regardless of the casing.
Expand All @@ -32,8 +32,16 @@ but not where they are overridden, as it can't be fixed locally there.
This also applies for pseudo-override patterns like CRTP.

``Leading_upper_snake_case`` is a naming convention where the first word is capitalized
followed by lower case word(s) seperated by underscore(s) '_'. Examples include:
Cap_snake_case, Cobra_case, Foo_bar_baz, and Master_copy_8gb.
followed by lower case word(s) separated by underscore(s) '_'. Examples include:
`Cap_snake_case`, `Cobra_case`, `Foo_bar_baz`, and `Master_copy_8gb`.

Hungarian notation can be customized using different *HungarianPrefix* settings.
The options and their corresponding values are:

- ``Off`` - the default setting
- ``On`` - example: ``int iVariable``
- ``LowerCase`` - example: ``int i_Variable``
- ``CamelCase`` - example: ``int IVariable``

Options
-------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,27 @@ int main() {

return 0;
}

namespace parameter_anaylysis {

struct S {
bool value(int) const;
bool leftValueRef(int &) const;
bool constRef(int const &) const;
bool rightValueRef(int &&) const;
};

void foo() {
S s{};
int i = 0;
assert(s.value(0));
assert(s.value(i));
assert(s.leftValueRef(i));
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: side effect in assert() condition discarded in release builds
assert(s.constRef(0));
assert(s.constRef(i));
assert(s.rightValueRef(0));
assert(s.rightValueRef(static_cast<int &&>(i)));
}

} // namespace parameter_anaylysis
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
// RUN: %check_clang_tidy -std=c++11-or-later %s bugprone-crtp-constructor-accessibility %t -- -- -fno-delayed-template-parsing

namespace class_implicit_ctor {
template <typename T>
class CRTP {};
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: the implicit default constructor of the CRTP is publicly accessible; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
// CHECK-FIXES: CRTP() = default;
// CHECK-FIXES: friend T;

class A : CRTP<A> {};
} // namespace class_implicit_ctor

namespace class_unconstructible {
template <typename T>
class CRTP {
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: the CRTP cannot be constructed from the derived class; consider declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
// CHECK-FIXES: friend T;
CRTP() = default;
};

class A : CRTP<A> {};
} // namespace class_unconstructible

namespace class_public_default_ctor {
template <typename T>
class CRTP {
public:
CRTP() = default;
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: public contructor allows the CRTP to be constructed as a regular template class; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
// CHECK-FIXES: private:{{[[:space:]]*}}CRTP() = default;{{[[:space:]]*}}public:
// CHECK-FIXES: friend T;
};

class A : CRTP<A> {};
} // namespace class_public_default_ctor

namespace class_public_user_provided_ctor {
template <typename T>
class CRTP {
public:
CRTP(int) {}
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: public contructor allows the CRTP to be constructed as a regular template class; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
// CHECK-FIXES: private:{{[[:space:]]*}}CRTP(int) {}{{[[:space:]]*}}public:
// CHECK-FIXES: friend T;
};

class A : CRTP<A> {};
} // namespace class_public_user_provided_ctor

namespace class_public_multiple_user_provided_ctors {
template <typename T>
class CRTP {
public:
CRTP(int) {}
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: public contructor allows the CRTP to be constructed as a regular template class; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
// CHECK-FIXES: private:{{[[:space:]]*}}CRTP(int) {}{{[[:space:]]*}}public:
CRTP(float) {}
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: public contructor allows the CRTP to be constructed as a regular template class; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
// CHECK-FIXES: private:{{[[:space:]]*}}CRTP(float) {}{{[[:space:]]*}}public:

// CHECK-FIXES: friend T;
// CHECK-FIXES: friend T;
};

class A : CRTP<A> {};
} // namespace class_public_multiple_user_provided_ctors

namespace class_protected_ctors {
template <typename T>
class CRTP {
protected:
CRTP(int) {}
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: protected contructor allows the CRTP to be inherited from as a regular template class; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
// CHECK-FIXES: private:{{[[:space:]]*}}CRTP(int) {}{{[[:space:]]*}}protected:
CRTP() = default;
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: protected contructor allows the CRTP to be inherited from as a regular template class; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
// CHECK-FIXES: private:{{[[:space:]]*}}CRTP() = default;{{[[:space:]]*}}protected:
CRTP(float) {}
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: protected contructor allows the CRTP to be inherited from as a regular template class; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
// CHECK-FIXES: private:{{[[:space:]]*}}CRTP(float) {}{{[[:space:]]*}}protected:

// CHECK-FIXES: friend T;
// CHECK-FIXES: friend T;
// CHECK-FIXES: friend T;
};

class A : CRTP<A> {};
} // namespace class_protected_ctors

namespace struct_implicit_ctor {
template <typename T>
struct CRTP {};
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: the implicit default constructor of the CRTP is publicly accessible; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
// CHECK-FIXES: private:{{[[:space:]]*}}CRTP() = default;{{[[:space:]]*}}public:
// CHECK-FIXES: friend T;

class A : CRTP<A> {};
} // namespace struct_implicit_ctor

namespace struct_default_ctor {
template <typename T>
struct CRTP {
CRTP() = default;
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: public contructor allows the CRTP to be constructed as a regular template class; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
// CHECK-FIXES: private:{{[[:space:]]*}}CRTP() = default;{{[[:space:]]*}}public:
// CHECK-FIXES: friend T;
};

class A : CRTP<A> {};
} // namespace struct_default_ctor

namespace same_class_multiple_crtps {
template <typename T>
struct CRTP {};
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: the implicit default constructor of the CRTP is publicly accessible; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
// CHECK-FIXES: private:{{[[:space:]]*}}CRTP() = default;{{[[:space:]]*}}public:
// CHECK-FIXES: friend T;

template <typename T>
struct CRTP2 {};
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: the implicit default constructor of the CRTP is publicly accessible; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
// CHECK-FIXES: private:{{[[:space:]]*}}CRTP2() = default;{{[[:space:]]*}}public:
// CHECK-FIXES: friend T;

class A : CRTP<A>, CRTP2<A> {};
} // namespace same_class_multiple_crtps

namespace same_crtp_multiple_classes {
template <typename T>
class CRTP {
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: the CRTP cannot be constructed from the derived class; consider declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
// CHECK-FIXES: friend T;
CRTP() = default;
};

class A : CRTP<A> {};
class B : CRTP<B> {};
} // namespace same_crtp_multiple_classes

namespace crtp_template {
template <typename T, typename U>
class CRTP {
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: the CRTP cannot be constructed from the derived class; consider declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
// CHECK-FIXES: friend U;
CRTP() = default;
};

class A : CRTP<int, A> {};
} // namespace crtp_template

namespace crtp_template2 {
template <typename T, typename U>
class CRTP {
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: the CRTP cannot be constructed from the derived class; consider declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
// CHECK-FIXES: friend T;
CRTP() = default;
};

class A : CRTP<A, A> {};
} // namespace crtp_template2

namespace template_derived {
template <typename T>
class CRTP {};
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: the implicit default constructor of the CRTP is publicly accessible; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
// CHECK-FIXES: CRTP() = default;
// CHECK-FIXES: friend T;

template<typename T>
class A : CRTP<A<T>> {};

// FIXME: Ideally the warning should be triggered without instantiation.
void foo() {
A<int> A;
(void) A;
}
} // namespace template_derived

namespace template_derived_explicit_specialization {
template <typename T>
class CRTP {};
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: the implicit default constructor of the CRTP is publicly accessible; consider making it private and declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
// CHECK-FIXES: CRTP() = default;
// CHECK-FIXES: friend T;

template<typename T>
class A : CRTP<A<T>> {};

template<>
class A<int> : CRTP<A<int>> {};
} // namespace template_derived_explicit_specialization

namespace explicit_derived_friend {
class A;

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

class A : CRTP<A> {};
} // namespace explicit_derived_friend

namespace explicit_derived_friend_multiple {
class A;

template <typename T>
class CRTP {
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: the CRTP cannot be constructed from the derived class; consider declaring the derived class as friend [bugprone-crtp-constructor-accessibility]
// CHECK-FIXES: friend T;
CRTP() = default;
friend A;
};

class A : CRTP<A> {};
class B : CRTP<B> {};
} // namespace explicit_derived_friend_multiple

namespace no_need_for_friend {
class A;

template <typename T>
class CRTP {
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: the implicit default constructor of the CRTP is publicly accessible; consider making it private [bugprone-crtp-constructor-accessibility]
// CHECK-FIXES: CRTP() = default;
friend A;
};

class A : CRTP<A> {};
} // namespace no_need_for_friend

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

class A : CRTP<A> {};
} // namespace no_warning

namespace no_warning_unsupported {
template<typename... Types>
class CRTP
{};

class A : CRTP<A> {};

void foo() {
A A;
(void) A;
}
} // namespace no_warning_unsupported
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// RUN: %check_clang_tidy %s bugprone-unused-return-value %t \
// RUN: -config='{CheckOptions: \
// RUN: {bugprone-unused-return-value.CheckedFunctions: \
// RUN: "::fun;::ns::Outer::Inner::memFun;::ns::Type::staticFun;::ns::ClassTemplate::memFun;::ns::ClassTemplate::staticFun"}}' \
// RUN: "::fun;::ns::Outer::Inner::memFun;::ns::Type::staticFun;::ns::ClassTemplate::(mem|static)Fun"}}' \
// RUN: --

namespace std {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ struct default_delete;

template <typename T, typename Deleter = std::default_delete<T>>
struct unique_ptr {
unique_ptr();
unique_ptr(unique_ptr const&);
unique_ptr(unique_ptr &&);
unique_ptr& operator=(unique_ptr const&);
unique_ptr& operator=(unique_ptr &&);
T *release() noexcept;
};

Expand Down Expand Up @@ -254,3 +259,12 @@ void noWarning() {
({ std::async(increment, 42); });
auto StmtExprRetval = ({ std::async(increment, 42); });
}

namespace gh84314 {

extern std::unique_ptr<int> alloc();
void f1(std::unique_ptr<int> &foo) {
foo = alloc();
}

} // namespace gh84314
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,18 @@ constexpr typename std::remove_reference<_Tp>::type &&move(_Tp &&__t) noexcept {
return static_cast<typename remove_reference<_Tp>::type &&>(__t);
}

template <class _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept {
return static_cast<_Tp&&>(__t);
}

template <class _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept {
return static_cast<_Tp&&>(__t);
}

} // namespace std

class A {
Expand Down Expand Up @@ -1525,3 +1537,28 @@ class PR38187 {
private:
std::string val_;
};

namespace issue82023
{

struct S {
S();
S(S&&);
};

void consume(S s);

template <typename T>
void forward(T&& t) {
consume(std::forward<T>(t));
consume(std::forward<T>(t));
// CHECK-NOTES: [[@LINE-1]]:27: warning: 't' used after it was forwarded
// CHECK-NOTES: [[@LINE-3]]:11: note: forward occurred here
}

void create() {
S s;
forward(std::move(s));
}

} // namespace issue82023
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,16 @@ void lambda_value_capture_copy(T&& t) {
[&,t]() { T other = std::forward<T>(t); };
}

template <typename X>
void use(const X &x) {}

template <typename X, typename Y>
void foo(X &&x, Y &&y) {
// CHECK-MESSAGES: :[[@LINE-1]]:21: warning: forwarding reference parameter 'y' is never forwarded inside the function body [cppcoreguidelines-missing-std-forward]
use(std::forward<X>(x));
use(y);
}

} // namespace positive_cases

namespace negative_cases {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// RUN: %check_clang_tidy %s google-explicit-constructor %t -std=c++20-or-later

namespace issue_81121
{

static constexpr bool ConstFalse = false;
static constexpr bool ConstTrue = true;

struct A {
explicit(true) A(int);
};

struct B {
explicit(false) B(int);
};

struct C {
explicit(ConstTrue) C(int);
};

struct D {
explicit(ConstFalse) D(int);
// CHECK-MESSAGES: :[[@LINE-1]]:24: warning: single-argument constructors explicit expression evaluates to 'false' [google-explicit-constructor]
};

template <typename>
struct E {
explicit(true) E(int);
};

template <typename>
struct F {
explicit(false) F(int);
};

template <typename>
struct G {
explicit(ConstTrue) G(int);
};

template <typename>
struct H {
explicit(ConstFalse) H(int);
// CHECK-MESSAGES: :[[@LINE-1]]:24: warning: single-argument constructors explicit expression evaluates to 'false' [google-explicit-constructor]
};

template <int Val>
struct I {
explicit(Val > 0) I(int);
};

template <int Val>
struct J {
explicit(Val > 0) J(int);
};

void useJ(J<0>, J<100>);

} // namespace issue_81121
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
Checks: readability-identifier-naming
CheckOptions:
readability-identifier-naming.AbstractClassCase: CamelCase
readability-identifier-naming.StructCase: CamelCase
readability-identifier-naming.UnionCase: camelBack
readability-identifier-naming.ClassCase: CamelCase
readability-identifier-naming.ClassConstantCase: CamelCase
readability-identifier-naming.ClassMemberCase: CamelCase
readability-identifier-naming.ConstantCase: CamelCase
readability-identifier-naming.ConstantMemberCase: CamelCase
readability-identifier-naming.ConstantParameterCase: CamelCase
readability-identifier-naming.ConstantPointerParameterCase: CamelCase
readability-identifier-naming.ConstexprVariableCase: CamelCase
readability-identifier-naming.EnumConstantCase: CamelCase
readability-identifier-naming.GlobalConstantCase: CamelCase
readability-identifier-naming.GlobalConstantPointerCase: CamelCase
readability-identifier-naming.GlobalPointerCase: CamelCase
readability-identifier-naming.GlobalVariableCase: CamelCase
readability-identifier-naming.LocalConstantCase: CamelCase
readability-identifier-naming.LocalConstantPointerCase: CamelCase
readability-identifier-naming.LocalPointerCase: CamelCase
readability-identifier-naming.LocalVariableCase: CamelCase
readability-identifier-naming.MemberCase: CamelCase
readability-identifier-naming.ParameterCase: CamelCase
readability-identifier-naming.PointerParameterCase: CamelCase
readability-identifier-naming.PrivateMemberCase: CamelCase
readability-identifier-naming.ProtectedMemberCase: CamelCase
readability-identifier-naming.PublicMemberCase: CamelCase
readability-identifier-naming.ScopedEnumConstantCase: CamelCase
readability-identifier-naming.StaticConstantCase: CamelCase
readability-identifier-naming.StaticVariableCase: CamelCase
readability-identifier-naming.VariableCase: CamelCase
readability-identifier-naming.AbstractClassHungarianPrefix: LowerCase
readability-identifier-naming.ClassHungarianPrefix: LowerCase
readability-identifier-naming.ClassConstantHungarianPrefix: LowerCase
readability-identifier-naming.ClassMemberHungarianPrefix: LowerCase
readability-identifier-naming.ConstantHungarianPrefix: LowerCase
readability-identifier-naming.ConstantMemberHungarianPrefix: LowerCase
readability-identifier-naming.ConstantParameterHungarianPrefix: LowerCase
readability-identifier-naming.ConstantPointerParameterHungarianPrefix: LowerCase
readability-identifier-naming.ConstexprVariableHungarianPrefix: LowerCase
readability-identifier-naming.EnumConstantHungarianPrefix: LowerCase
readability-identifier-naming.GlobalConstantHungarianPrefix: LowerCase
readability-identifier-naming.GlobalConstantPointerHungarianPrefix: LowerCase
readability-identifier-naming.GlobalPointerHungarianPrefix: LowerCase
readability-identifier-naming.GlobalVariableHungarianPrefix: LowerCase
readability-identifier-naming.LocalConstantHungarianPrefix: LowerCase
readability-identifier-naming.LocalConstantPointerHungarianPrefix: LowerCase
readability-identifier-naming.LocalPointerHungarianPrefix: LowerCase
readability-identifier-naming.LocalVariableHungarianPrefix: LowerCase
readability-identifier-naming.MemberHungarianPrefix: LowerCase
readability-identifier-naming.ParameterHungarianPrefix: LowerCase
readability-identifier-naming.PointerParameterHungarianPrefix: LowerCase
readability-identifier-naming.PrivateMemberHungarianPrefix: LowerCase
readability-identifier-naming.ProtectedMemberHungarianPrefix: LowerCase
readability-identifier-naming.PublicMemberHungarianPrefix: LowerCase
readability-identifier-naming.ScopedEnumConstantHungarianPrefix: LowerCase
readability-identifier-naming.StaticConstantHungarianPrefix: LowerCase
readability-identifier-naming.StaticVariableHungarianPrefix: LowerCase
readability-identifier-naming.VariableHungarianPrefix: LowerCase
Loading