63 changes: 63 additions & 0 deletions bolt/test/X86/match-blocks-with-pseudo-probes.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
## Tests stale block matching with pseudo probes.

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

# CHECK: BOLT-INFO: inference found an exact pseudo probe match for 100.00% of basic blocks (1 out of 1 stale)

#--- main.s
.text
.globl main # -- Begin function main
.p2align 4, 0x90
.type main,@function
main: # @main
# %bb.0:
pushq %rbp
movq %rsp, %rbp
movl $0, -4(%rbp)
.pseudoprobe 15822663052811949562 1 0 0 main
xorl %eax, %eax
popq %rbp
retq
.Lfunc_end0:
.size main, .Lfunc_end0-main
# -- End function
.section .pseudo_probe_desc,"",@progbits
.quad -2624081020897602054
.quad 4294967295
.byte 4
.ascii "main"

#--- yaml
---
header:
profile-version: 1
binary-name: 'match-blocks-with-pseudo-probes.s.tmp.exe'
binary-build-id: '<unknown>'
profile-flags: [ lbr ]
profile-origin: branch profile reader
profile-events: ''
dfs-order: false
hash-func: xxh3
functions:
- name: main
fid: 0
hash: 0x0000000000000001
exec: 1
nblocks: 6
blocks:
- bid: 1
hash: 0xFFFFFFFFFFFFFFF1
insns: 1
succ: [ { bid: 3, cnt: 1} ]
probes: [ { blx: 1 } ]
inline_tree: [ { g: 0 } ]
pseudo_probe_desc:
gs: [ 0xDB956436E78DD5FA ]
gh: [ 0 ]
hs: [ 0xFFFFFFFF ]
2 changes: 1 addition & 1 deletion bolt/test/X86/match-functions-with-call-graph.test
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# REQUIRES: system-linux
# RUN: split-file %s %t
# RUN: %clang %cflags %t/main.cpp -o %t.exe -Wl,-q -nostdlib
# RUN: %clangxx %cxxflags %t/main.cpp -o %t.exe -Wl,-q -nostdlib
# RUN: llvm-bolt %t.exe -o %t.out --data %t/yaml --profile-ignore-hash -v=1 \
# RUN: --dyno-stats --print-cfg --infer-stale-profile=1 --match-with-call-graph 2>&1 | FileCheck %s

Expand Down
7 changes: 4 additions & 3 deletions bolt/test/X86/match-functions-with-calls-as-anchors.test
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@
# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-unknown %t/main.s -o %t.o
# RUN: %clang %cflags %t.o -o %t.exe -Wl,-q -nostdlib
# RUN: llvm-bolt %t.exe -o %t.out --data %t/yaml --profile-ignore-hash -v=1 \
# RUN: --dyno-stats --print-cfg --infer-stale-profile=1 --debug 2>&1 | FileCheck %s
# RUN: --dyno-stats --print-cfg --infer-stale-profile=1 --debug-only=bolt-prof \
# RUN: 2>&1 | FileCheck %s

# CHECK: BOLT-INFO: applying profile inference for "qux"
# CHECK: Matched yaml block (bid = 1) with hash 4 to BB (index = 0) with hash 314e1bc10000
# CHECK: loose match
# CHECK: call match

# CHECK: BOLT-INFO: applying profile inference for "fred"
# CHECK: Matched yaml block (bid = 1) with hash 5 to BB (index = 0) with hash 7541bc10000
# CHECK: loose match
# CHECK: call match

#--- main.s
.globl foo # -- Begin function foo
Expand Down
86 changes: 86 additions & 0 deletions bolt/test/X86/pie-eh-split-undo.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# REQUIRES: system-linux

# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-linux %s -o %t.o
# RUN: link_fdata %s %t.o %t.fdata
# RUN: llvm-strip --strip-unneeded %t.o
# RUN: ld.lld --pie %t.o -o %t.exe -q
# RUN: llvm-bolt %t.exe -o %t.out --data %t.fdata --split-functions --split-eh \
# RUN: --split-all-cold --print-after-lowering --print-only=_start 2>&1 \
# RUN: | FileCheck %s

## _start has two landing pads: one hot and one cold. Hence, BOLT will introduce
## a landing pad trampoline. However, the trampoline code will make the main
## split fragment larger than the whole function before split. Then BOLT will
## undo the splitting and remove the trampoline.

# CHECK: Binary Function "_start"
# CHECK: IsSplit :
# CHECK-SAME: 0

## Check that a landing pad trampoline was created, but contains no instructions
## and falls though to the real landing pad.

# CHECK: {{^[^[:space:]]+}} (0 instructions
# CHECK-NEXT: Landing Pad{{$}}
# CHECK: Exec Count
# CHECK-SAME: : 0
# CHECK: Successors:
# CHECK-SAME: [[LP:[^[:space:]]+]]
# CHECK-EMPTY:
# CHECK-NEXT: [[LP]]

.text
.global foo
.type foo, %function
foo:
.cfi_startproc
ret
.cfi_endproc
.size foo, .-foo

.globl _start
.type _start, %function
_start:
# FDATA: 0 [unknown] 0 1 _start 0 1 100
.Lfunc_begin0:
.cfi_startproc
.cfi_lsda 27, .Lexception0
call foo
.Ltmp0:
call foo
.Ltmp1:
ret

## Cold landing pad.
.LLP1:
ret

## Hot landing pad.
LLP0:
# FDATA: 0 [unknown] 0 1 _start #LLP0# 1 100
ret

.cfi_endproc
.Lfunc_end0:
.size _start, .-_start

## EH table.
.section .gcc_except_table,"a",@progbits
.p2align 2
GCC_except_table0:
.Lexception0:
.byte 255 # @LPStart Encoding = omit
.byte 255 # @TType Encoding = omit
.byte 1 # Call site Encoding = uleb128
.uleb128 .Lcst_end0-.Lcst_begin0
.Lcst_begin0:
.uleb128 .Lfunc_begin0-.Lfunc_begin0 # >> Call Site 1 <<
.uleb128 .Ltmp0-.Lfunc_begin0 # Call between .Lfunc_begin0 and .Ltmp0
.uleb128 LLP0-.Lfunc_begin0 # jumps to LLP0
.byte 0 # On action: cleanup
.uleb128 .Ltmp0-.Lfunc_begin0 # >> Call Site 2 <<
.uleb128 .Ltmp1-.Ltmp0 # Call between .Ltmp0 and .Ltmp1
.uleb128 .LLP1-.Lfunc_begin0 # jumps to .LLP1
.byte 0 # On action: cleanup
.Lcst_end0:

4 changes: 2 additions & 2 deletions bolt/test/X86/reader-stale-yaml.test
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@ CHECK2: pre-processing profile using YAML profile reader
CHECK2: applying profile inference for "SolveCubic"
CHECK2: Matched yaml block (bid = 0) with hash 4600940a609c0000 to BB (index = 0) with hash 4600940a609c0000
CHECK2-NEXT: exact match
CHECK2: Matched yaml block (bid = 1) with hash 167a1f084f130088 to BB (index = 1) with hash 167a1f084f130088
CHECK2-NEXT: exact match
CHECK2: Matched yaml block (bid = 13) with hash a8d50000f81902a7 to BB (index = 13) with hash a8d5aa43f81902a7
CHECK2-NEXT: loose match
CHECK2: Matched yaml block (bid = 1) with hash 167a1f084f130088 to BB (index = 1) with hash 167a1f084f130088
CHECK2-NEXT: exact match
CHECK2: Matched yaml block (bid = 3) with hash c516000073dc00a0 to BB (index = 3) with hash c516b1c973dc00a0
CHECK2-NEXT: loose match
CHECK2: Matched yaml block (bid = 5) with hash 6446e1ea500111 to BB (index = 5) with hash 6446e1ea500111
Expand Down
12 changes: 12 additions & 0 deletions bolt/test/eh-frame-hdr.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Check that llvm-bolt overwrites .eh_frame_hdr in-place.

REQUIRES: system-linux

RUN: %clang %cflags %p/Inputs/hello.c -o %t -Wl,-q
RUN: llvm-bolt %t -o %t.bolt --use-old-text \
RUN: | FileCheck %s --check-prefix=CHECK-BOLT
RUN: llvm-readelf -WS %t.bolt | FileCheck %s

CHECK-BOLT: rewriting .eh_frame_hdr in-place

CHECK-NOT: .bolt.org.eh_frame_hdr
8 changes: 8 additions & 0 deletions bolt/test/eh-frame-overwrite.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Check that llvm-bolt can overwrite .eh_frame section in-place.

REQUIRES: system-linux

RUN: %clang %cflags %p/Inputs/hello.c -o %t -Wl,-q
RUN: llvm-bolt %t -o %t.bolt --strict | FileCheck %s

CHECK: rewriting .eh_frame in-place
2 changes: 1 addition & 1 deletion bolt/test/pie.test
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
## on Linux systems where the host triple matches the target.
REQUIRES: system-linux

RUN: %clang %cflags -fPIC -pie %p/Inputs/jump_table_icp.cpp -o %t
RUN: %clangxx %cxxflags -fPIC -pie %p/Inputs/jump_table_icp.cpp -o %t
RUN: llvm-bolt %t -o %t.null 2>&1 | FileCheck %s

CHECK: BOLT-INFO: shared object or position-independent executable detected
2 changes: 1 addition & 1 deletion bolt/test/runtime/X86/Inputs/pie-exceptions-failed-split.s
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Assembly generated from building the followingC++ code with the following
# Assembly generated from building the following C++ code with the following
# command using trunk clang. Then, basic block at .LBB1_7 was moved before the
# landing pad.
#
Expand Down
2 changes: 1 addition & 1 deletion bolt/test/runtime/X86/instrumentation-indirect.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ int main(int argc, char **argv) {
/*
REQUIRES: system-linux,bolt-runtime,lit-max-individual-test-time
RUN: %clang %cflags %s -o %t.exe -Wl,-q -pie -fpie
RUN: %clang %cflags -D_GNU_SOURCE %s -o %t.exe -Wl,-q -pie -fpie
RUN: llvm-bolt %t.exe --instrument --instrumentation-file=%t.fdata \
RUN: --instrumentation-wait-forks=1 --conservative-instrumentation \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,16 @@ RUN: llvm-bolt %t -o %t.bolt --data %t.fdata --reorder-blocks=ext-tsp \
RUN: --split-functions --split-eh --print-after-lowering \
RUN: --print-only=_Z10throw_testiPPc 2>&1 | FileCheck %s

## Hot code in the test case gets larger after splitting because of jump
## instruction relaxation. Check that BOLT reverts the split correctly.
## Check that a landing pad is split from its thrower and does not require a
## trampoline LP.
CHECK: Binary Function "_Z10throw_testiPPc"
CHECK: IsSplit :
CHECK-SAME: 0

## Check that the landing pad trampoline was created, but contains no
## instructions and falls to the real landing pad.
CHECK: {{^[^[:space:]]+}} (0 instructions
CHECK-NEXT: Landing Pad{{$}}
CHECK: Exec Count
CHECK-SAME: : 0
CHECK: Successors:
CHECK-SAME: [[LP:[^[:space:]]+]]
CHECK-EMPTY:
CHECK-NEXT: [[LP]]
CHECK-DAG: Exec Count
CHECK-NOT: Exec Count
CHECK-DAG: callq __cxa_begin_catch
CHECK-SAME: 1
CHECK: callq {{.*}} # handler: [[LPAD:.*]];
CHECK-NOT: Landing Pad{{$}}
CHECK: HOT-COLD SPLIT POINT
CHECK: {{^}}[[LPAD]]
CHECK-NEXT: Landing Pad

## Verify the output still executes correctly when the exception path is being
## taken.
Expand Down
4 changes: 2 additions & 2 deletions bolt/test/runtime/bolt-reserved.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* new sections.
*/

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

Expand All @@ -16,7 +16,7 @@
* not enough for allocating new sections.
*/

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

Expand Down
3 changes: 3 additions & 0 deletions bolt/unittests/Core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ set(LLVM_LINK_COMPONENTS
add_bolt_unittest(CoreTests
BinaryContext.cpp
MCPlusBuilder.cpp
MemoryMaps.cpp
DynoStats.cpp

DISABLE_LLVM_LINK_LLVM_DYLIB
Expand All @@ -17,6 +18,8 @@ target_link_libraries(CoreTests
PRIVATE
LLVMBOLTCore
LLVMBOLTRewrite
LLVMBOLTProfile
LLVMTestingSupport
)

foreach (tgt ${BOLT_TARGETS_TO_BUILD})
Expand Down
5 changes: 3 additions & 2 deletions bolt/unittests/Core/MCPlusBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,15 @@ INSTANTIATE_TEST_SUITE_P(AArch64, MCPlusBuilderTester,
::testing::Values(Triple::aarch64));

TEST_P(MCPlusBuilderTester, AliasX0) {
uint64_t AliasesX0[] = {AArch64::W0, AArch64::X0, AArch64::W0_W1,
uint64_t AliasesX0[] = {AArch64::W0, AArch64::W0_HI,
AArch64::X0, AArch64::W0_W1,
AArch64::X0_X1, AArch64::X0_X1_X2_X3_X4_X5_X6_X7};
size_t AliasesX0Count = sizeof(AliasesX0) / sizeof(*AliasesX0);
testRegAliases(Triple::aarch64, AArch64::X0, AliasesX0, AliasesX0Count);
}

TEST_P(MCPlusBuilderTester, AliasSmallerX0) {
uint64_t AliasesX0[] = {AArch64::W0, AArch64::X0};
uint64_t AliasesX0[] = {AArch64::W0, AArch64::W0_HI, AArch64::X0};
size_t AliasesX0Count = sizeof(AliasesX0) / sizeof(*AliasesX0);
testRegAliases(Triple::aarch64, AArch64::X0, AliasesX0, AliasesX0Count, true);
}
Expand Down
142 changes: 142 additions & 0 deletions bolt/unittests/Core/MemoryMaps.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
//===- bolt/unittest/Core/MemoryMaps.cpp ----------------------------------===//
//
// 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 "bolt/Core/BinaryContext.h"
#include "bolt/Profile/DataAggregator.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/DebugInfo/DWARF/DWARFContext.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Testing/Support/Error.h"
#include "gtest/gtest.h"

using namespace llvm;
using namespace llvm::object;
using namespace llvm::ELF;
using namespace bolt;

namespace opts {
extern cl::opt<std::string> ReadPerfEvents;
} // namespace opts

namespace {

/// Perform checks on memory map events normally captured in perf. Tests use
/// the 'opts::ReadPerfEvents' flag to emulate these events, passing a custom
/// 'perf script' output to DataAggregator.
struct MemoryMapsTester : public testing::TestWithParam<Triple::ArchType> {
void SetUp() override {
initalizeLLVM();
prepareElf();
initializeBOLT();
}

protected:
void initalizeLLVM() {
llvm::InitializeAllTargetInfos();
llvm::InitializeAllTargetMCs();
llvm::InitializeAllAsmParsers();
llvm::InitializeAllDisassemblers();
llvm::InitializeAllTargets();
llvm::InitializeAllAsmPrinters();
}

void prepareElf() {
memcpy(ElfBuf, "\177ELF", 4);
ELF64LE::Ehdr *EHdr = reinterpret_cast<typename ELF64LE::Ehdr *>(ElfBuf);
EHdr->e_ident[llvm::ELF::EI_CLASS] = llvm::ELF::ELFCLASS64;
EHdr->e_ident[llvm::ELF::EI_DATA] = llvm::ELF::ELFDATA2LSB;
EHdr->e_machine = GetParam() == Triple::aarch64 ? EM_AARCH64 : EM_X86_64;
MemoryBufferRef Source(StringRef(ElfBuf, sizeof(ElfBuf)), "ELF");
ObjFile = cantFail(ObjectFile::createObjectFile(Source));
}

void initializeBOLT() {
Relocation::Arch = ObjFile->makeTriple().getArch();
BC = cantFail(BinaryContext::createBinaryContext(
ObjFile->makeTriple(), ObjFile->getFileName(), nullptr, true,
DWARFContext::create(*ObjFile.get()), {llvm::outs(), llvm::errs()}));
ASSERT_FALSE(!BC);
}

char ElfBuf[sizeof(typename ELF64LE::Ehdr)] = {};
std::unique_ptr<ObjectFile> ObjFile;
std::unique_ptr<BinaryContext> BC;
};
} // namespace

#ifdef X86_AVAILABLE

INSTANTIATE_TEST_SUITE_P(X86, MemoryMapsTester,
::testing::Values(Triple::x86_64));

#endif

#ifdef AARCH64_AVAILABLE

INSTANTIATE_TEST_SUITE_P(AArch64, MemoryMapsTester,
::testing::Values(Triple::aarch64));

#endif

/// Check that the correct mmap size is computed when we have multiple text
/// segment mappings.
TEST_P(MemoryMapsTester, ParseMultipleSegments) {
const int Pid = 1234;
StringRef Filename = "BINARY";
opts::ReadPerfEvents = formatv(
"name 0 [000] 0.000000: PERF_RECORD_MMAP2 {0}/{0}: "
"[0xabc0000000(0x1000000) @ 0x11c0000 103:01 1573523 0]: r-xp {1}\n"
"name 0 [000] 0.000000: PERF_RECORD_MMAP2 {0}/{0}: "
"[0xabc2000000(0x8000000) @ 0x31d0000 103:01 1573523 0]: r-xp {1}\n",
Pid, Filename);

BC->SegmentMapInfo[0x11da000] =
SegmentInfo{0x11da000, 0x10da000, 0x11ca000, 0x10da000, 0x10000, true};
BC->SegmentMapInfo[0x31d0000] =
SegmentInfo{0x31d0000, 0x51ac82c, 0x31d0000, 0x3000000, 0x200000, true};

DataAggregator DA("");
BC->setFilename(Filename);
Error Err = DA.preprocessProfile(*BC);

// Ignore errors from perf2bolt when parsing memory events later on.
ASSERT_THAT_ERROR(std::move(Err), Succeeded());

auto &BinaryMMapInfo = DA.getBinaryMMapInfo();
auto El = BinaryMMapInfo.find(Pid);
// Check that memory mapping is present and has the expected size.
ASSERT_NE(El, BinaryMMapInfo.end());
ASSERT_EQ(El->second.Size, static_cast<uint64_t>(0xb1d0000));
}

/// Check that DataAggregator aborts when pre-processing an input binary
/// with multiple text segments that have different base addresses.
TEST_P(MemoryMapsTester, MultipleSegmentsMismatchedBaseAddress) {
const int Pid = 1234;
StringRef Filename = "BINARY";
opts::ReadPerfEvents = formatv(
"name 0 [000] 0.000000: PERF_RECORD_MMAP2 {0}/{0}: "
"[0xabc0000000(0x1000000) @ 0x11c0000 103:01 1573523 0]: r-xp {1}\n"
"name 0 [000] 0.000000: PERF_RECORD_MMAP2 {0}/{0}: "
"[0xabc2000000(0x8000000) @ 0x31d0000 103:01 1573523 0]: r-xp {1}\n",
Pid, Filename);

BC->SegmentMapInfo[0x11da000] =
SegmentInfo{0x11da000, 0x10da000, 0x11ca000, 0x10da000, 0x10000, true};
// Using '0x31d0fff' FileOffset which triggers a different base address
// for this second text segment.
BC->SegmentMapInfo[0x31d0000] =
SegmentInfo{0x31d0000, 0x51ac82c, 0x31d0fff, 0x3000000, 0x200000, true};

DataAggregator DA("");
BC->setFilename(Filename);
ASSERT_DEBUG_DEATH(
{ Error Err = DA.preprocessProfile(*BC); },
"Base address on multiple segment mappings should match");
}
4 changes: 2 additions & 2 deletions bolt/utils/bughunter.sh
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ if [[ $FAIL -eq "0" ]]; then
fi
else
echo "Did it pass? Type the return code [0 = pass, 1 = fail]"
read -n1 PASS
read -n1 FAIL
fi
if [[ $FAIL -eq "0" ]] ; then
echo " Warning: optimized binary passes."
Expand Down Expand Up @@ -205,7 +205,7 @@ while [[ "$CONTINUE" -ne "0" ]] ; do
echo " OPTIMIZED_BINARY failure=$FAIL"
else
echo "Did it pass? Type the return code [0 = pass, 1 = fail]"
read -n1 PASS
read -n1 FAIL
fi
else
FAIL=1
Expand Down
30 changes: 0 additions & 30 deletions clang-tools-extra/CODE_OWNERS.TXT

This file was deleted.

75 changes: 75 additions & 0 deletions clang-tools-extra/Maintainers.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
=============================
Clang Tools Extra Maintainers
=============================

This file is a list of the maintainers
(https://llvm.org/docs/DeveloperPolicy.html#maintainers) for clang-tools-extra.


Active Maintainers
==================
The following people are the active maintainers for the project. Please reach
out to them for code reviews, questions about their area of expertise, or other
assistance.

Lead Maintainer
---------------
| Aaron Ballman
| aaron@aaronballman.com (email), aaron.ballman (Phabricator), AaronBallman (GitHub), AaronBallman (Discourse), aaronballman (Discord), AaronBallman (IRC)


clang-tidy
----------
| Congcong Cai
| congcongcai0907@163.com (email), HerrCai0907 (GitHub), HerrCai0907 (Discourse)

| Julian Schmidt
| git.julian.schmidt@gmail.com (email), 5chmidti (GitHub), 5chmidti (Discourse), 5chmidti (Discord)

| Piotr Zegar
| me@piotrzegar.pl (email), PiotrZSL (GitHub), PiotrZSL (Discourse), PiotrZSL (Discord)


clang-query
-----------
| Aaron Ballman
| aaron@aaronballman.com (email), aaron.ballman (Phabricator), AaronBallman (GitHub), AaronBallman (Discourse), aaronballman (Discord), AaronBallman (IRC)


clang-doc
---------
| Paul Kirth
| paulkirth@google.com (email), ilovepi (GitHub), ilovepi (Discourse)

| Peter Chou
| peterchou411@gmail.com (email), PeterChou1 (GitHub), PeterChou1 (Discourse), .peterchou (Discord)


clangd
------
| Nathan Ridge
| zeratul976@hotmail.com (email), HighCommander4 (GitHub), HighCommander4 (Discourse), nridge (Discord)

| Chris Bieneman
| chris.bieneman@gmail.com (email), llvm-beanz (GitHub), beanz (Discord), beanz (Discourse)

| Kadir Çetinkaya
| kadircet@google.com (email), kadircet (GitHub) kadircet (Discourse), kadircet (Discord)


Inactive Maintainers
====================
The following people have graciously spent time performing maintainership
responsibilities but are no longer active in that role. Thank you for all your
help with the success of the project!

Emeritus Lead Maintainers
-------------------------
| Manuel Klimek (klimek@google.com (email), r4nt (GitHub))


Inactive component maintainers
------------------------------
| Nathan James (n.james93@hotmail.co.uk) -- clang-tidy
| Julie Hockett (juliehockett@google.com) -- clang-doc
| Sam McCall (sammccall@google.com (email), sam-mccall (GitHub, Discourse, Discord)) -- clangd
3 changes: 2 additions & 1 deletion clang-tools-extra/clang-include-fixer/IncludeFixer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ bool IncludeFixerActionFactory::runInvocation(

// Create the compiler's actual diagnostics engine. We want to drop all
// diagnostics here.
Compiler.createDiagnostics(new clang::IgnoringDiagConsumer,
Compiler.createDiagnostics(Files->getVirtualFileSystem(),
new clang::IgnoringDiagConsumer,
/*ShouldOwnClient=*/true);
Compiler.createSourceManager(*Files);

Expand Down
29 changes: 26 additions & 3 deletions clang-tools-extra/clang-reorder-fields/ReorderFieldsAction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,28 @@ findMembersUsedInInitExpr(const CXXCtorInitializer *Initializer,
return Results;
}

/// Returns the full source range for the field declaration up to (not
/// including) the trailing semicolumn, including potential macro invocations,
/// e.g. `int a GUARDED_BY(mu);`.
static SourceRange getFullFieldSourceRange(const FieldDecl &Field,
const ASTContext &Context) {
SourceRange Range = Field.getSourceRange();
SourceLocation End = Range.getEnd();
const SourceManager &SM = Context.getSourceManager();
const LangOptions &LangOpts = Context.getLangOpts();
while (true) {
std::optional<Token> CurrentToken = Lexer::findNextToken(End, SM, LangOpts);

if (!CurrentToken || CurrentToken->is(tok::semi))
break;

if (CurrentToken->is(tok::eof))
return Range; // Something is wrong, return the original range.
End = CurrentToken->getLastLoc();
}
return SourceRange(Range.getBegin(), End);
}

/// Reorders fields in the definition of a struct/class.
///
/// At the moment reordering of fields with
Expand Down Expand Up @@ -145,9 +167,10 @@ static bool reorderFieldsInDefinition(
const auto FieldIndex = Field->getFieldIndex();
if (FieldIndex == NewFieldsOrder[FieldIndex])
continue;
addReplacement(Field->getSourceRange(),
Fields[NewFieldsOrder[FieldIndex]]->getSourceRange(),
Context, Replacements);
addReplacement(
getFullFieldSourceRange(*Field, Context),
getFullFieldSourceRange(*Fields[NewFieldsOrder[FieldIndex]], Context),
Context, Replacements);
}
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ ExpandModularHeadersPPCallbacks::ExpandModularHeadersPPCallbacks(
Diags.setSourceManager(&Sources);
// FIXME: Investigate whatever is there better way to initialize DiagEngine
// or whatever DiagEngine can be shared by multiple preprocessors
ProcessWarningOptions(Diags, Compiler.getDiagnosticOpts());
ProcessWarningOptions(Diags, Compiler.getDiagnosticOpts(),
Compiler.getVirtualFileSystem());

LangOpts.Modules = false;

Expand Down
221 changes: 221 additions & 0 deletions clang-tools-extra/clang-tidy/bugprone/BranchCloneCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,212 @@ void BranchCloneCheck::registerMatchers(MatchFinder *Finder) {
this);
Finder->addMatcher(switchStmt().bind("switch"), this);
Finder->addMatcher(conditionalOperator().bind("condOp"), this);
Finder->addMatcher(
ifStmt((hasThen(hasDescendant(ifStmt())))).bind("ifWithDescendantIf"),
this);
}

/// Determines whether two statement trees are identical regarding
/// operators and symbols.
///
/// Exceptions: expressions containing macros or functions with possible side
/// effects are never considered identical.
/// Limitations: (t + u) and (u + t) are not considered identical.
/// t*(u + t) and t*u + t*t are not considered identical.
///
static bool isIdenticalStmt(const ASTContext &Ctx, const Stmt *Stmt1,
const Stmt *Stmt2, bool IgnoreSideEffects) {

if (!Stmt1 || !Stmt2)
return !Stmt1 && !Stmt2;

// If Stmt1 & Stmt2 are of different class then they are not
// identical statements.
if (Stmt1->getStmtClass() != Stmt2->getStmtClass())
return false;

const auto *Expr1 = dyn_cast<Expr>(Stmt1);
const auto *Expr2 = dyn_cast<Expr>(Stmt2);

if (Expr1 && Expr2) {
// If Stmt1 has side effects then don't warn even if expressions
// are identical.
if (!IgnoreSideEffects && Expr1->HasSideEffects(Ctx) &&
Expr2->HasSideEffects(Ctx))
return false;
// If either expression comes from a macro then don't warn even if
// the expressions are identical.
if ((Expr1->getExprLoc().isMacroID()) || (Expr2->getExprLoc().isMacroID()))
return false;

// If all children of two expressions are identical, return true.
Expr::const_child_iterator I1 = Expr1->child_begin();
Expr::const_child_iterator I2 = Expr2->child_begin();
while (I1 != Expr1->child_end() && I2 != Expr2->child_end()) {
if (!isIdenticalStmt(Ctx, *I1, *I2, IgnoreSideEffects))
return false;
++I1;
++I2;
}
// If there are different number of children in the statements, return
// false.
if (I1 != Expr1->child_end())
return false;
if (I2 != Expr2->child_end())
return false;
}

switch (Stmt1->getStmtClass()) {
default:
return false;
case Stmt::CallExprClass:
case Stmt::ArraySubscriptExprClass:
case Stmt::ArraySectionExprClass:
case Stmt::OMPArrayShapingExprClass:
case Stmt::OMPIteratorExprClass:
case Stmt::ImplicitCastExprClass:
case Stmt::ParenExprClass:
case Stmt::BreakStmtClass:
case Stmt::ContinueStmtClass:
case Stmt::NullStmtClass:
return true;
case Stmt::CStyleCastExprClass: {
const auto *CastExpr1 = cast<CStyleCastExpr>(Stmt1);
const auto *CastExpr2 = cast<CStyleCastExpr>(Stmt2);

return CastExpr1->getTypeAsWritten() == CastExpr2->getTypeAsWritten();
}
case Stmt::ReturnStmtClass: {
const auto *ReturnStmt1 = cast<ReturnStmt>(Stmt1);
const auto *ReturnStmt2 = cast<ReturnStmt>(Stmt2);

return isIdenticalStmt(Ctx, ReturnStmt1->getRetValue(),
ReturnStmt2->getRetValue(), IgnoreSideEffects);
}
case Stmt::ForStmtClass: {
const auto *ForStmt1 = cast<ForStmt>(Stmt1);
const auto *ForStmt2 = cast<ForStmt>(Stmt2);

if (!isIdenticalStmt(Ctx, ForStmt1->getInit(), ForStmt2->getInit(),
IgnoreSideEffects))
return false;
if (!isIdenticalStmt(Ctx, ForStmt1->getCond(), ForStmt2->getCond(),
IgnoreSideEffects))
return false;
if (!isIdenticalStmt(Ctx, ForStmt1->getInc(), ForStmt2->getInc(),
IgnoreSideEffects))
return false;
if (!isIdenticalStmt(Ctx, ForStmt1->getBody(), ForStmt2->getBody(),
IgnoreSideEffects))
return false;
return true;
}
case Stmt::DoStmtClass: {
const auto *DStmt1 = cast<DoStmt>(Stmt1);
const auto *DStmt2 = cast<DoStmt>(Stmt2);

if (!isIdenticalStmt(Ctx, DStmt1->getCond(), DStmt2->getCond(),
IgnoreSideEffects))
return false;
if (!isIdenticalStmt(Ctx, DStmt1->getBody(), DStmt2->getBody(),
IgnoreSideEffects))
return false;
return true;
}
case Stmt::WhileStmtClass: {
const auto *WStmt1 = cast<WhileStmt>(Stmt1);
const auto *WStmt2 = cast<WhileStmt>(Stmt2);

if (!isIdenticalStmt(Ctx, WStmt1->getCond(), WStmt2->getCond(),
IgnoreSideEffects))
return false;
if (!isIdenticalStmt(Ctx, WStmt1->getBody(), WStmt2->getBody(),
IgnoreSideEffects))
return false;
return true;
}
case Stmt::IfStmtClass: {
const auto *IStmt1 = cast<IfStmt>(Stmt1);
const auto *IStmt2 = cast<IfStmt>(Stmt2);

if (!isIdenticalStmt(Ctx, IStmt1->getCond(), IStmt2->getCond(),
IgnoreSideEffects))
return false;
if (!isIdenticalStmt(Ctx, IStmt1->getThen(), IStmt2->getThen(),
IgnoreSideEffects))
return false;
if (!isIdenticalStmt(Ctx, IStmt1->getElse(), IStmt2->getElse(),
IgnoreSideEffects))
return false;
return true;
}
case Stmt::CompoundStmtClass: {
const auto *CompStmt1 = cast<CompoundStmt>(Stmt1);
const auto *CompStmt2 = cast<CompoundStmt>(Stmt2);

if (CompStmt1->size() != CompStmt2->size())
return false;

if (!llvm::all_of(llvm::zip(CompStmt1->body(), CompStmt2->body()),
[&Ctx, IgnoreSideEffects](
std::tuple<const Stmt *, const Stmt *> stmtPair) {
const Stmt *stmt0 = std::get<0>(stmtPair);
const Stmt *stmt1 = std::get<1>(stmtPair);
return isIdenticalStmt(Ctx, stmt0, stmt1,
IgnoreSideEffects);
})) {
return false;
}

return true;
}
case Stmt::CompoundAssignOperatorClass:
case Stmt::BinaryOperatorClass: {
const auto *BinOp1 = cast<BinaryOperator>(Stmt1);
const auto *BinOp2 = cast<BinaryOperator>(Stmt2);
return BinOp1->getOpcode() == BinOp2->getOpcode();
}
case Stmt::CharacterLiteralClass: {
const auto *CharLit1 = cast<CharacterLiteral>(Stmt1);
const auto *CharLit2 = cast<CharacterLiteral>(Stmt2);
return CharLit1->getValue() == CharLit2->getValue();
}
case Stmt::DeclRefExprClass: {
const auto *DeclRef1 = cast<DeclRefExpr>(Stmt1);
const auto *DeclRef2 = cast<DeclRefExpr>(Stmt2);
return DeclRef1->getDecl() == DeclRef2->getDecl();
}
case Stmt::IntegerLiteralClass: {
const auto *IntLit1 = cast<IntegerLiteral>(Stmt1);
const auto *IntLit2 = cast<IntegerLiteral>(Stmt2);

llvm::APInt I1 = IntLit1->getValue();
llvm::APInt I2 = IntLit2->getValue();
if (I1.getBitWidth() != I2.getBitWidth())
return false;
return I1 == I2;
}
case Stmt::FloatingLiteralClass: {
const auto *FloatLit1 = cast<FloatingLiteral>(Stmt1);
const auto *FloatLit2 = cast<FloatingLiteral>(Stmt2);
return FloatLit1->getValue().bitwiseIsEqual(FloatLit2->getValue());
}
case Stmt::StringLiteralClass: {
const auto *StringLit1 = cast<StringLiteral>(Stmt1);
const auto *StringLit2 = cast<StringLiteral>(Stmt2);
return StringLit1->getBytes() == StringLit2->getBytes();
}
case Stmt::MemberExprClass: {
const auto *MemberStmt1 = cast<MemberExpr>(Stmt1);
const auto *MemberStmt2 = cast<MemberExpr>(Stmt2);
return MemberStmt1->getMemberDecl() == MemberStmt2->getMemberDecl();
}
case Stmt::UnaryOperatorClass: {
const auto *UnaryOp1 = cast<UnaryOperator>(Stmt1);
const auto *UnaryOp2 = cast<UnaryOperator>(Stmt2);
return UnaryOp1->getOpcode() == UnaryOp2->getOpcode();
}
}
}

void BranchCloneCheck::check(const MatchFinder::MatchResult &Result) {
Expand Down Expand Up @@ -269,6 +475,21 @@ void BranchCloneCheck::check(const MatchFinder::MatchResult &Result) {
return;
}

if (const auto *IS = Result.Nodes.getNodeAs<IfStmt>("ifWithDescendantIf")) {
const Stmt *Then = IS->getThen();
auto CS = dyn_cast<CompoundStmt>(Then);
if (CS && (!CS->body_empty())) {
const auto *InnerIf = dyn_cast<IfStmt>(*CS->body_begin());
if (InnerIf && isIdenticalStmt(Context, IS->getCond(), InnerIf->getCond(),
/*IgnoreSideEffects=*/false)) {
diag(IS->getBeginLoc(), "if with identical inner if statement");
diag(InnerIf->getBeginLoc(), "inner if starts here",
DiagnosticIDs::Note);
}
}
return;
}

llvm_unreachable("No if statement and no switch statement.");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ namespace {

AST_MATCHER_P(FunctionDecl, isEnabled, llvm::StringSet<>,
FunctionsThatShouldNotThrow) {
return FunctionsThatShouldNotThrow.count(Node.getNameAsString()) > 0;
return FunctionsThatShouldNotThrow.contains(Node.getNameAsString());
}

AST_MATCHER(FunctionDecl, isExplicitThrow) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ void InfiniteLoopCheck::check(const MatchFinder::MatchResult &Result) {
}
}

if (ExprMutationAnalyzer::isUnevaluated(LoopStmt, *LoopStmt, *Result.Context))
if (ExprMutationAnalyzer::isUnevaluated(LoopStmt, *Result.Context))
return;

if (isAtLeastOneCondVarChanged(Func, LoopStmt, Cond, Result.Context))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ void ReturnConstRefFromParameterCheck::registerMatchers(MatchFinder *Finder) {
to(parmVarDecl(hasType(hasCanonicalType(
qualType(lValueReferenceType(pointee(
qualType(isConstQualified()))))
.bind("type"))))
.bind("type"))),
hasDeclContext(functionDecl().bind("owner")))
.bind("param")))
.bind("dref"));
const auto Func =
functionDecl(hasReturnTypeLoc(loc(
functionDecl(equalsBoundNode("owner"),
hasReturnTypeLoc(loc(
qualType(hasCanonicalType(equalsBoundNode("type"))))))
.bind("func");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,9 @@ void SizeofExpressionCheck::check(const MatchFinder::MatchResult &Result) {
"suspicious usage of 'sizeof(array)/sizeof(...)';"
" denominator differs from the size of array elements")
<< E->getLHS()->getSourceRange() << E->getRHS()->getSourceRange();
} else if (NumTy && DenomTy && NumTy == DenomTy) {
} else if (NumTy && DenomTy && NumTy == DenomTy &&
!NumTy->isDependentType()) {
// Dependent type should not be compared.
diag(E->getOperatorLoc(),
"suspicious usage of 'sizeof(...)/sizeof(...)'; both expressions "
"have the same type")
Expand Down
9 changes: 5 additions & 4 deletions clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -315,9 +315,10 @@ void UseAfterMoveFinder::getReinits(
"::std::unordered_map", "::std::unordered_multiset",
"::std::unordered_multimap"))))));

auto StandardSmartPointerTypeMatcher = hasType(hasUnqualifiedDesugaredType(
recordType(hasDeclaration(cxxRecordDecl(hasAnyName(
"::std::unique_ptr", "::std::shared_ptr", "::std::weak_ptr"))))));
auto StandardResettableOwnerTypeMatcher = hasType(
hasUnqualifiedDesugaredType(recordType(hasDeclaration(cxxRecordDecl(
hasAnyName("::std::unique_ptr", "::std::shared_ptr",
"::std::weak_ptr", "::std::optional", "::std::any"))))));

// Matches different types of reinitialization.
auto ReinitMatcher =
Expand All @@ -340,7 +341,7 @@ void UseAfterMoveFinder::getReinits(
callee(cxxMethodDecl(hasAnyName("clear", "assign")))),
// reset() on standard smart pointers.
cxxMemberCallExpr(
on(expr(DeclRefMatcher, StandardSmartPointerTypeMatcher)),
on(expr(DeclRefMatcher, StandardResettableOwnerTypeMatcher)),
callee(cxxMethodDecl(hasName("reset")))),
// Methods that have the [[clang::reinitializes]] attribute.
cxxMemberCallExpr(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ static bool checkOverridingFunctionReturnType(const ASTContext *Context,

// The class type D should have the same cv-qualification as or less
// cv-qualification than the class type B.
if (DTy.isMoreQualifiedThan(BTy))
if (DTy.isMoreQualifiedThan(BTy, *Context))
return false;

return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,79 +13,88 @@
using namespace clang::ast_matchers;

namespace clang::tidy::cppcoreguidelines {
namespace {

AST_MATCHER(FieldDecl, isMemberOfLambda) {
return Node.getParent()->isLambda();
static bool isCopyConstructible(CXXRecordDecl const &Node) {
if (Node.needsOverloadResolutionForCopyConstructor() &&
Node.needsImplicitCopyConstructor()) {
// unresolved
for (CXXBaseSpecifier const &BS : Node.bases()) {
CXXRecordDecl const *BRD = BS.getType()->getAsCXXRecordDecl();
if (BRD != nullptr && !isCopyConstructible(*BRD))
return false;
}
}
if (Node.hasSimpleCopyConstructor())
return true;
for (CXXConstructorDecl const *Ctor : Node.ctors())
if (Ctor->isCopyConstructor())
return !Ctor->isDeleted();
return false;
}

struct MemberFunctionInfo {
bool Declared{};
bool Deleted{};
};

struct MemberFunctionPairInfo {
MemberFunctionInfo Copy{};
MemberFunctionInfo Move{};
};

MemberFunctionPairInfo getConstructorsInfo(CXXRecordDecl const &Node) {
MemberFunctionPairInfo Constructors{};

for (CXXConstructorDecl const *Ctor : Node.ctors()) {
if (Ctor->isCopyConstructor()) {
Constructors.Copy.Declared = true;
if (Ctor->isDeleted())
Constructors.Copy.Deleted = true;
}
if (Ctor->isMoveConstructor()) {
Constructors.Move.Declared = true;
if (Ctor->isDeleted())
Constructors.Move.Deleted = true;
static bool isMoveConstructible(CXXRecordDecl const &Node) {
if (Node.needsOverloadResolutionForMoveConstructor() &&
Node.needsImplicitMoveConstructor()) {
// unresolved
for (CXXBaseSpecifier const &BS : Node.bases()) {
CXXRecordDecl const *BRD = BS.getType()->getAsCXXRecordDecl();
if (BRD != nullptr && !isMoveConstructible(*BRD))
return false;
}
}

return Constructors;
if (Node.hasSimpleMoveConstructor())
return true;
for (CXXConstructorDecl const *Ctor : Node.ctors())
if (Ctor->isMoveConstructor())
return !Ctor->isDeleted();
return false;
}

MemberFunctionPairInfo getAssignmentsInfo(CXXRecordDecl const &Node) {
MemberFunctionPairInfo Assignments{};

for (CXXMethodDecl const *Method : Node.methods()) {
if (Method->isCopyAssignmentOperator()) {
Assignments.Copy.Declared = true;
if (Method->isDeleted())
Assignments.Copy.Deleted = true;
static bool isCopyAssignable(CXXRecordDecl const &Node) {
if (Node.needsOverloadResolutionForCopyAssignment() &&
Node.needsImplicitCopyAssignment()) {
// unresolved
for (CXXBaseSpecifier const &BS : Node.bases()) {
CXXRecordDecl const *BRD = BS.getType()->getAsCXXRecordDecl();
if (BRD != nullptr && !isCopyAssignable(*BRD))
return false;
}
}
if (Node.hasSimpleCopyAssignment())
return true;
for (CXXMethodDecl const *Method : Node.methods())
if (Method->isCopyAssignmentOperator())
return !Method->isDeleted();
return false;
}

if (Method->isMoveAssignmentOperator()) {
Assignments.Move.Declared = true;
if (Method->isDeleted())
Assignments.Move.Deleted = true;
static bool isMoveAssignable(CXXRecordDecl const &Node) {
if (Node.needsOverloadResolutionForMoveAssignment() &&
Node.needsImplicitMoveAssignment()) {
// unresolved
for (CXXBaseSpecifier const &BS : Node.bases()) {
CXXRecordDecl const *BRD = BS.getType()->getAsCXXRecordDecl();
if (BRD != nullptr && !isMoveAssignable(*BRD))
return false;
}
}

return Assignments;
if (Node.hasSimpleMoveAssignment())
return true;
for (CXXMethodDecl const *Method : Node.methods())
if (Method->isMoveAssignmentOperator())
return !Method->isDeleted();
return false;
}

AST_MATCHER(CXXRecordDecl, isCopyableOrMovable) {
MemberFunctionPairInfo Constructors = getConstructorsInfo(Node);
MemberFunctionPairInfo Assignments = getAssignmentsInfo(Node);
namespace {

if (Node.hasSimpleCopyConstructor() ||
(Constructors.Copy.Declared && !Constructors.Copy.Deleted))
return true;
if (Node.hasSimpleMoveConstructor() ||
(Constructors.Move.Declared && !Constructors.Move.Deleted))
return true;
if (Node.hasSimpleCopyAssignment() ||
(Assignments.Copy.Declared && !Assignments.Copy.Deleted))
return true;
if (Node.hasSimpleMoveAssignment() ||
(Assignments.Move.Declared && !Assignments.Move.Deleted))
return true;
AST_MATCHER(FieldDecl, isMemberOfLambda) {
return Node.getParent()->isLambda();
}

return false;
AST_MATCHER(CXXRecordDecl, isCopyableOrMovable) {
return isCopyConstructible(Node) || isMoveConstructible(Node) ||
isCopyAssignable(Node) || isMoveAssignable(Node);
}

} // namespace
Expand Down
70 changes: 54 additions & 16 deletions clang-tools-extra/clang-tidy/misc/RedundantExpressionCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -855,9 +855,6 @@ static bool areExprsMacroAndNonMacro(const Expr *&LhsExpr,
} // namespace

void RedundantExpressionCheck::registerMatchers(MatchFinder *Finder) {
const auto AnyLiteralExpr = ignoringParenImpCasts(
anyOf(cxxBoolLiteral(), characterLiteral(), integerLiteral()));

const auto BannedIntegerLiteral =
integerLiteral(expandedByMacro(KnownBannedMacroNames));
const auto IsInUnevaluatedContext = expr(anyOf(
Expand All @@ -866,19 +863,16 @@ void RedundantExpressionCheck::registerMatchers(MatchFinder *Finder) {
// Binary with equivalent operands, like (X != 2 && X != 2).
Finder->addMatcher(
traverse(TK_AsIs,
binaryOperator(
anyOf(isComparisonOperator(),
hasAnyOperatorName("-", "/", "%", "|", "&", "^", "&&",
"||", "=")),
operandsAreEquivalent(),
// Filter noisy false positives.
unless(isInTemplateInstantiation()),
unless(binaryOperatorIsInMacro()),
unless(hasType(realFloatingPointType())),
unless(hasEitherOperand(hasType(realFloatingPointType()))),
unless(hasLHS(AnyLiteralExpr)),
unless(hasDescendant(BannedIntegerLiteral)),
unless(IsInUnevaluatedContext))
binaryOperator(anyOf(isComparisonOperator(),
hasAnyOperatorName("-", "/", "%", "|", "&",
"^", "&&", "||", "=")),
operandsAreEquivalent(),
// Filter noisy false positives.
unless(isInTemplateInstantiation()),
unless(binaryOperatorIsInMacro()),
unless(hasAncestor(arraySubscriptExpr())),
unless(hasDescendant(BannedIntegerLiteral)),
unless(IsInUnevaluatedContext))
.bind("binary")),
this);

Expand Down Expand Up @@ -1238,6 +1232,50 @@ void RedundantExpressionCheck::check(const MatchFinder::MatchResult &Result) {
if (const auto *BinOp = Result.Nodes.getNodeAs<BinaryOperator>("binary")) {
// If the expression's constants are macros, check whether they are
// intentional.

//
// Special case for floating-point representation.
//
// If expressions on both sides of comparison operator are of type float,
// then for some comparison operators no warning shall be
// reported even if the expressions are identical from a symbolic point of
// view. Comparison between expressions, declared variables and literals
// are treated differently.
//
// != and == between float literals that have the same value should NOT
// warn. < > between float literals that have the same value SHOULD warn.
//
// != and == between the same float declaration should NOT warn.
// < > between the same float declaration SHOULD warn.
//
// != and == between eq. expressions that evaluates into float
// should NOT warn.
// < > between eq. expressions that evaluates into float
// should NOT warn.
//
const Expr *LHS = BinOp->getLHS()->IgnoreParenImpCasts();
const Expr *RHS = BinOp->getRHS()->IgnoreParenImpCasts();
const BinaryOperator::Opcode Op = BinOp->getOpcode();
const bool OpEqualEQorNE = ((Op == BO_EQ) || (Op == BO_NE));

const auto *DeclRef1 = dyn_cast<DeclRefExpr>(LHS);
const auto *DeclRef2 = dyn_cast<DeclRefExpr>(RHS);
const auto *FloatLit1 = dyn_cast<FloatingLiteral>(LHS);
const auto *FloatLit2 = dyn_cast<FloatingLiteral>(RHS);

if (DeclRef1 && DeclRef2 &&
DeclRef1->getType()->hasFloatingRepresentation() &&
DeclRef2->getType()->hasFloatingRepresentation() &&
(DeclRef1->getDecl() == DeclRef2->getDecl()) && OpEqualEQorNE) {
return;
}

if (FloatLit1 && FloatLit2 &&
FloatLit1->getValue().bitwiseIsEqual(FloatLit2->getValue()) &&
OpEqualEQorNE) {
return;
}

if (areSidesBinaryConstExpressions(BinOp, Result.Context)) {
const Expr *LhsConst = nullptr, *RhsConst = nullptr;
BinaryOperatorKind MainOpcode{}, SideOpcode{};
Expand Down
34 changes: 29 additions & 5 deletions clang-tools-extra/clang-tidy/misc/UseInternalLinkageCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@

#include "UseInternalLinkageCheck.h"
#include "../utils/FileExtensionsUtils.h"
#include "../utils/LexerUtils.h"
#include "clang/AST/Decl.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchersMacros.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/Specifiers.h"
#include "clang/Basic/TokenKinds.h"
#include "clang/Lex/Token.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"

using namespace clang::ast_matchers;

Expand Down Expand Up @@ -47,6 +47,8 @@ namespace {

AST_MATCHER(Decl, isFirstDecl) { return Node.isFirstDecl(); }

AST_MATCHER(FunctionDecl, hasBody) { return Node.hasBody(); }

static bool isInMainFile(SourceLocation L, SourceManager &SM,
const FileExtensionsSet &HeaderFileExtensions) {
for (;;) {
Expand Down Expand Up @@ -78,6 +80,22 @@ AST_POLYMORPHIC_MATCHER(isExternStorageClass,
return Node.getStorageClass() == SC_Extern;
}

AST_MATCHER(FunctionDecl, isAllocationOrDeallocationOverloadedFunction) {
// [basic.stc.dynamic.allocation]
// An allocation function that is not a class member function shall belong to
// the global scope and not have a name with internal linkage.
// [basic.stc.dynamic.deallocation]
// A deallocation function that is not a class member function shall belong to
// the global scope and not have a name with internal linkage.
static const llvm::DenseSet<OverloadedOperatorKind> OverloadedOperators{
OverloadedOperatorKind::OO_New,
OverloadedOperatorKind::OO_Array_New,
OverloadedOperatorKind::OO_Delete,
OverloadedOperatorKind::OO_Array_Delete,
};
return OverloadedOperators.contains(Node.getOverloadedOperator());
}

} // namespace

UseInternalLinkageCheck::UseInternalLinkageCheck(StringRef Name,
Expand All @@ -100,10 +118,16 @@ void UseInternalLinkageCheck::registerMatchers(MatchFinder *Finder) {
isExternStorageClass(), isExternC(),
// 3. template
isExplicitTemplateSpecialization(),
// 4. friend
hasAncestor(friendDecl()))));
hasAncestor(decl(anyOf(
// 4. friend
friendDecl(),
// 5. module export decl
exportDecl()))))));
Finder->addMatcher(
functionDecl(Common, unless(cxxMethodDecl()), unless(isMain()))
functionDecl(Common, hasBody(),
unless(anyOf(cxxMethodDecl(),
isAllocationOrDeallocationOverloadedFunction(),
isMain())))
.bind("fn"),
this);
Finder->addMatcher(varDecl(Common, hasGlobalStorage()).bind("var"), this);
Expand Down
76 changes: 55 additions & 21 deletions clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,21 @@
using namespace clang::ast_matchers;

namespace clang::tidy::modernize {

static bool isNegativeComparison(const Expr *ComparisonExpr) {
if (const auto *Op = llvm::dyn_cast<BinaryOperator>(ComparisonExpr))
return Op->getOpcode() == BO_NE;

if (const auto *Op = llvm::dyn_cast<CXXOperatorCallExpr>(ComparisonExpr))
return Op->getOperator() == OO_ExclaimEqual;

if (const auto *Op =
llvm::dyn_cast<CXXRewrittenBinaryOperator>(ComparisonExpr))
return Op->getOperator() == BO_NE;

return false;
}

struct NotLengthExprForStringNode {
NotLengthExprForStringNode(std::string ID, DynTypedNode Node,
ASTContext *Context)
Expand Down Expand Up @@ -171,10 +186,26 @@ void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {
hasRHS(lengthExprForStringNode("needle")))))
.bind("expr"),
this);

// Case 6: X.substr(0, LEN(Y)) [!=]= Y -> starts_with.
Finder->addMatcher(
binaryOperation(
hasAnyOperatorName("==", "!="),
hasOperands(
expr().bind("needle"),
cxxMemberCallExpr(
argumentCountIs(2), hasArgument(0, ZeroLiteral),
hasArgument(1, lengthExprForStringNode("needle")),
callee(cxxMethodDecl(hasName("substr"),
ofClass(OnClassWithStartsWithFunction))
.bind("find_fun")))
.bind("find_expr")))
.bind("expr"),
this);
}

void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
const auto *ComparisonExpr = Result.Nodes.getNodeAs<BinaryOperator>("expr");
const auto *ComparisonExpr = Result.Nodes.getNodeAs<Expr>("expr");
const auto *FindExpr = Result.Nodes.getNodeAs<CXXMemberCallExpr>("find_expr");
const auto *FindFun = Result.Nodes.getNodeAs<CXXMethodDecl>("find_fun");
const auto *SearchExpr = Result.Nodes.getNodeAs<Expr>("needle");
Expand All @@ -183,39 +214,42 @@ void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
const auto *EndsWithFunction =
Result.Nodes.getNodeAs<CXXMethodDecl>("ends_with_fun");
assert(bool(StartsWithFunction) != bool(EndsWithFunction));

const CXXMethodDecl *ReplacementFunction =
StartsWithFunction ? StartsWithFunction : EndsWithFunction;

if (ComparisonExpr->getBeginLoc().isMacroID())
if (ComparisonExpr->getBeginLoc().isMacroID() ||
FindExpr->getBeginLoc().isMacroID())
return;

const bool Neg = ComparisonExpr->getOpcode() == BO_NE;
// Make sure FindExpr->getArg(0) can be used to make a range in the FitItHint.
if (FindExpr->getNumArgs() == 0)
return;

auto Diagnostic =
diag(FindExpr->getExprLoc(), "use %0 instead of %1() %select{==|!=}2 0")
<< ReplacementFunction->getName() << FindFun->getName() << Neg;
// Retrieve the source text of the search expression.
const auto SearchExprText = Lexer::getSourceText(
CharSourceRange::getTokenRange(SearchExpr->getSourceRange()),
*Result.SourceManager, Result.Context->getLangOpts());

// Remove possible arguments after search expression and ' [!=]= .+' suffix.
Diagnostic << FixItHint::CreateReplacement(
CharSourceRange::getTokenRange(
Lexer::getLocForEndOfToken(SearchExpr->getEndLoc(), 0,
*Result.SourceManager, getLangOpts()),
ComparisonExpr->getEndLoc()),
")");
auto Diagnostic = diag(FindExpr->getExprLoc(), "use %0 instead of %1")
<< ReplacementFunction->getName() << FindFun->getName();

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

// Replace method name by '(starts|ends)_with'.
// Remove possible arguments before search expression.
// Rename the function to `starts_with` or `ends_with`.
Diagnostic << FixItHint::CreateReplacement(FindExpr->getExprLoc(),
ReplacementFunction->getName());

// Replace arguments and everything after the function call.
Diagnostic << FixItHint::CreateReplacement(
CharSourceRange::getCharRange(FindExpr->getExprLoc(),
SearchExpr->getBeginLoc()),
(ReplacementFunction->getName() + "(").str());
CharSourceRange::getTokenRange(FindExpr->getArg(0)->getBeginLoc(),
ComparisonExpr->getEndLoc()),
(SearchExprText + ")").str());

// Add possible negation '!'.
if (Neg)
// Add negation if necessary.
if (isNegativeComparison(ComparisonExpr))
Diagnostic << FixItHint::CreateInsertion(FindExpr->getBeginLoc(), "!");
}

Expand Down
3 changes: 2 additions & 1 deletion clang-tools-extra/clang-tidy/modernize/UseStdPrintCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ AST_MATCHER(StringLiteral, isOrdinary) { return Node.isOrdinary(); }
} // namespace

UseStdPrintCheck::UseStdPrintCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
: ClangTidyCheck(Name, Context), PP(nullptr),
StrictMode(Options.getLocalOrGlobal("StrictMode", false)),
PrintfLikeFunctions(utils::options::parseStringList(
Options.get("PrintfLikeFunctions", ""))),
Expand Down Expand Up @@ -131,6 +131,7 @@ void UseStdPrintCheck::check(const MatchFinder::MatchResult &Result) {
utils::FormatStringConverter::Configuration ConverterConfig;
ConverterConfig.StrictMode = StrictMode;
ConverterConfig.AllowTrailingNewlineRemoval = true;
assert(PP && "Preprocessor should be set by registerPPCallbacks");
utils::FormatStringConverter Converter(
Result.Context, Printf, FormatArgOffset, ConverterConfig, getLangOpts(),
*Result.SourceManager, *PP);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,10 +299,11 @@ static bool applyDiceHeuristic(StringRef Arg, StringRef Param,

/// Checks if ArgType binds to ParamType regarding reference-ness and
/// cv-qualifiers.
static bool areRefAndQualCompatible(QualType ArgType, QualType ParamType) {
static bool areRefAndQualCompatible(QualType ArgType, QualType ParamType,
const ASTContext &Ctx) {
return !ParamType->isReferenceType() ||
ParamType.getNonReferenceType().isAtLeastAsQualifiedAs(
ArgType.getNonReferenceType());
ArgType.getNonReferenceType(), Ctx);
}

static bool isPointerOrArray(QualType TypeToCheck) {
Expand All @@ -311,12 +312,12 @@ static bool isPointerOrArray(QualType TypeToCheck) {

/// Checks whether ArgType is an array type identical to ParamType's array type.
/// Enforces array elements' qualifier compatibility as well.
static bool isCompatibleWithArrayReference(QualType ArgType,
QualType ParamType) {
static bool isCompatibleWithArrayReference(QualType ArgType, QualType ParamType,
const ASTContext &Ctx) {
if (!ArgType->isArrayType())
return false;
// Here, qualifiers belong to the elements of the arrays.
if (!ParamType.isAtLeastAsQualifiedAs(ArgType))
if (!ParamType.isAtLeastAsQualifiedAs(ArgType, Ctx))
return false;

return ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType();
Expand All @@ -342,12 +343,13 @@ static QualType convertToPointeeOrArrayElementQualType(QualType TypeToConvert) {
/// every * in ParamType to the right of that cv-qualifier, except the last
/// one, must also be const-qualified.
static bool arePointersStillQualCompatible(QualType ArgType, QualType ParamType,
bool &IsParamContinuouslyConst) {
bool &IsParamContinuouslyConst,
const ASTContext &Ctx) {
// The types are compatible, if the parameter is at least as qualified as the
// argument, and if it is more qualified, it has to be const on upper pointer
// levels.
bool AreTypesQualCompatible =
ParamType.isAtLeastAsQualifiedAs(ArgType) &&
ParamType.isAtLeastAsQualifiedAs(ArgType, Ctx) &&
(!ParamType.hasQualifiers() || IsParamContinuouslyConst);
// Check whether the parameter's constness continues at the current pointer
// level.
Expand All @@ -359,9 +361,10 @@ static bool arePointersStillQualCompatible(QualType ArgType, QualType ParamType,
/// Checks whether multilevel pointers are compatible in terms of levels,
/// qualifiers and pointee type.
static bool arePointerTypesCompatible(QualType ArgType, QualType ParamType,
bool IsParamContinuouslyConst) {
bool IsParamContinuouslyConst,
const ASTContext &Ctx) {
if (!arePointersStillQualCompatible(ArgType, ParamType,
IsParamContinuouslyConst))
IsParamContinuouslyConst, Ctx))
return false;

do {
Expand All @@ -372,7 +375,7 @@ static bool arePointerTypesCompatible(QualType ArgType, QualType ParamType,
// Check whether cv-qualifiers permit compatibility on
// current level.
if (!arePointersStillQualCompatible(ArgType, ParamType,
IsParamContinuouslyConst))
IsParamContinuouslyConst, Ctx))
return false;

if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType())
Expand All @@ -396,7 +399,7 @@ static bool areTypesCompatible(QualType ArgType, QualType ParamType,
return true;

// Check for constness and reference compatibility.
if (!areRefAndQualCompatible(ArgType, ParamType))
if (!areRefAndQualCompatible(ArgType, ParamType, Ctx))
return false;

bool IsParamReference = ParamType->isReferenceType();
Expand Down Expand Up @@ -434,7 +437,7 @@ static bool areTypesCompatible(QualType ArgType, QualType ParamType,
// When ParamType is an array reference, ArgType has to be of the same-sized
// array-type with cv-compatible element type.
if (IsParamReference && ParamType->isArrayType())
return isCompatibleWithArrayReference(ArgType, ParamType);
return isCompatibleWithArrayReference(ArgType, ParamType, Ctx);

bool IsParamContinuouslyConst =
!IsParamReference || ParamType.getNonReferenceType().isConstQualified();
Expand All @@ -444,7 +447,7 @@ static bool areTypesCompatible(QualType ArgType, QualType ParamType,
ParamType = convertToPointeeOrArrayElementQualType(ParamType);

// Check qualifier compatibility on the next level.
if (!ParamType.isAtLeastAsQualifiedAs(ArgType))
if (!ParamType.isAtLeastAsQualifiedAs(ArgType, Ctx))
return false;

if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType())
Expand Down Expand Up @@ -472,8 +475,8 @@ static bool areTypesCompatible(QualType ArgType, QualType ParamType,
if (!(ParamType->isAnyPointerType() && ArgType->isAnyPointerType()))
return false;

return arePointerTypesCompatible(ArgType, ParamType,
IsParamContinuouslyConst);
return arePointerTypesCompatible(ArgType, ParamType, IsParamContinuouslyConst,
Ctx);
}

static bool isOverloadedUnaryOrBinarySymbolOperator(const FunctionDecl *FD) {
Expand Down
13 changes: 10 additions & 3 deletions clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,12 @@ bool isQualificationConvertiblePointer(QualType From, QualType To,
} // namespace

static bool canThrow(const FunctionDecl *Func) {
// consteval specifies that every call to the function must produce a
// compile-time constant, which cannot evaluate a throw expression without
// producing a compilation error.
if (Func->isConsteval())
return false;

const auto *FunProto = Func->getType()->getAs<FunctionProtoType>();
if (!FunProto)
return true;
Expand Down Expand Up @@ -418,7 +424,7 @@ ExceptionAnalyzer::ExceptionInfo::filterIgnoredExceptions(
if (TD->getDeclName().isIdentifier()) {
if ((IgnoreBadAlloc &&
(TD->getName() == "bad_alloc" && TD->isInStdNamespace())) ||
(IgnoredTypes.count(TD->getName()) > 0))
(IgnoredTypes.contains(TD->getName())))
TypesToDelete.push_back(T);
}
}
Expand Down Expand Up @@ -449,7 +455,8 @@ void ExceptionAnalyzer::ExceptionInfo::reevaluateBehaviour() {
ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
const FunctionDecl *Func, const ExceptionInfo::Throwables &Caught,
llvm::SmallSet<const FunctionDecl *, 32> &CallStack) {
if (!Func || CallStack.count(Func) || (!CallStack.empty() && !canThrow(Func)))
if (!Func || CallStack.contains(Func) ||
(!CallStack.empty() && !canThrow(Func)))
return ExceptionInfo::createNonThrowing();

if (const Stmt *Body = Func->getBody()) {
Expand Down Expand Up @@ -507,7 +514,7 @@ ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
for (unsigned I = 0; I < Try->getNumHandlers(); ++I) {
const CXXCatchStmt *Catch = Try->getHandler(I);

// Everything is catched through 'catch(...)'.
// Everything is caught through 'catch(...)'.
if (!Catch->getExceptionDecl()) {
ExceptionInfo Rethrown = throwsException(
Catch->getHandlerBlock(), Uncaught.getExceptionTypes(), CallStack);
Expand Down
4 changes: 2 additions & 2 deletions clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ class ExceptionAnalyzer {
/// Recalculate the 'Behaviour' for example after filtering.
void reevaluateBehaviour();

/// Keep track if the entity related to this 'ExceptionInfo' can in princple
/// throw, if it's unknown or if it won't throw.
/// Keep track if the entity related to this 'ExceptionInfo' can in
/// principle throw, if it's unknown or if it won't throw.
State Behaviour;

/// Keep track if the entity contains any unknown elements to keep track
Expand Down
13 changes: 5 additions & 8 deletions clang-tools-extra/clangd/ClangdLSPServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1419,15 +1419,12 @@ void ClangdLSPServer::applyConfiguration(
const ConfigurationSettings &Settings) {
// Per-file update to the compilation database.
llvm::StringSet<> ModifiedFiles;
for (auto &Entry : Settings.compilationDatabaseChanges) {
PathRef File = Entry.first;
auto Old = CDB->getCompileCommand(File);
auto New =
tooling::CompileCommand(std::move(Entry.second.workingDirectory), File,
std::move(Entry.second.compilationCommand),
for (auto &[File, Command] : Settings.compilationDatabaseChanges) {
auto Cmd =
tooling::CompileCommand(std::move(Command.workingDirectory), File,
std::move(Command.compilationCommand),
/*Output=*/"");
if (Old != New) {
CDB->setCompileCommand(File, std::move(New));
if (CDB->setCompileCommand(File, std::move(Cmd))) {
ModifiedFiles.insert(File);
}
}
Expand Down
6 changes: 3 additions & 3 deletions clang-tools-extra/clangd/Compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ buildCompilerInvocation(const ParseInputs &Inputs, clang::DiagnosticConsumer &D,
CIOpts.VFS = Inputs.TFS->view(Inputs.CompileCommand.Directory);
CIOpts.CC1Args = CC1Args;
CIOpts.RecoverOnError = true;
CIOpts.Diags =
CompilerInstance::createDiagnostics(new DiagnosticOptions, &D, false);
CIOpts.Diags = CompilerInstance::createDiagnostics(
*CIOpts.VFS, new DiagnosticOptions, &D, false);
CIOpts.ProbePrecompiled = false;
std::unique_ptr<CompilerInvocation> CI = createInvocation(ArgStrs, CIOpts);
if (!CI)
Expand Down Expand Up @@ -148,7 +148,7 @@ prepareCompilerInstance(std::unique_ptr<clang::CompilerInvocation> CI,
auto Clang = std::make_unique<CompilerInstance>(
std::make_shared<PCHContainerOperations>());
Clang->setInvocation(std::move(CI));
Clang->createDiagnostics(&DiagsClient, false);
Clang->createDiagnostics(*VFS, &DiagsClient, false);

if (auto VFSWithRemapping = createVFSFromCompilerInvocation(
Clang->getInvocation(), Clang->getDiagnostics(), VFS))
Expand Down
15 changes: 11 additions & 4 deletions clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -807,20 +807,27 @@ tooling::CompileCommand OverlayCDB::getFallbackCommand(PathRef File) const {
return Cmd;
}

void OverlayCDB::setCompileCommand(PathRef File,
bool OverlayCDB::setCompileCommand(PathRef File,
std::optional<tooling::CompileCommand> Cmd) {
// We store a canonical version internally to prevent mismatches between set
// and get compile commands. Also it assures clients listening to broadcasts
// doesn't receive different names for the same file.
std::string CanonPath = removeDots(File);
{
std::unique_lock<std::mutex> Lock(Mutex);
if (Cmd)
Commands[CanonPath] = std::move(*Cmd);
else
if (Cmd) {
if (auto [It, Inserted] =
Commands.try_emplace(CanonPath, std::move(*Cmd));
!Inserted) {
if (It->second == *Cmd)
return false;
It->second = *Cmd;
}
} else
Commands.erase(CanonPath);
}
OnCommandChanged.broadcast({CanonPath});
return true;
}

DelegatingCDB::DelegatingCDB(const GlobalCompilationDatabase *Base)
Expand Down
4 changes: 3 additions & 1 deletion clang-tools-extra/clangd/GlobalCompilationDatabase.h
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,9 @@ class OverlayCDB : public DelegatingCDB {
tooling::CompileCommand getFallbackCommand(PathRef File) const override;

/// Sets or clears the compilation command for a particular file.
void
/// Returns true if the command was changed (including insertion and removal),
/// false if it was unchanged.
bool
setCompileCommand(PathRef File,
std::optional<tooling::CompileCommand> CompilationCommand);

Expand Down
13 changes: 9 additions & 4 deletions clang-tools-extra/clangd/InlayHints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -626,10 +626,15 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {

bool VisitLambdaExpr(LambdaExpr *E) {
FunctionDecl *D = E->getCallOperator();
if (!E->hasExplicitResultType())
addReturnTypeHint(D, E->hasExplicitParameters()
? D->getFunctionTypeLoc().getRParenLoc()
: E->getIntroducerRange().getEnd());
if (!E->hasExplicitResultType()) {
SourceLocation TypeHintLoc;
if (!E->hasExplicitParameters())
TypeHintLoc = E->getIntroducerRange().getEnd();
else if (auto FTL = D->getFunctionTypeLoc())
TypeHintLoc = FTL.getRParenLoc();
if (TypeHintLoc.isValid())
addReturnTypeHint(D, TypeHintLoc);
}
return true;
}

Expand Down
320 changes: 215 additions & 105 deletions clang-tools-extra/clangd/ModulesBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#include "clang/Frontend/FrontendActions.h"
#include "clang/Serialization/ASTReader.h"
#include "clang/Serialization/InMemoryModuleCache.h"
#include "llvm/ADT/ScopeExit.h"
#include <queue>

namespace clang {
namespace clangd {
Expand Down Expand Up @@ -124,10 +126,58 @@ struct ModuleFile {
llvm::sys::fs::remove(ModuleFilePath);
}

StringRef getModuleName() const { return ModuleName; }

StringRef getModuleFilePath() const { return ModuleFilePath; }

private:
std::string ModuleName;
std::string ModuleFilePath;
};

// ReusablePrerequisiteModules - stands for PrerequisiteModules for which all
// the required modules are built successfully. All the module files
// are owned by the modules builder.
class ReusablePrerequisiteModules : public PrerequisiteModules {
public:
ReusablePrerequisiteModules() = default;

ReusablePrerequisiteModules(const ReusablePrerequisiteModules &Other) =
default;
ReusablePrerequisiteModules &
operator=(const ReusablePrerequisiteModules &) = default;
ReusablePrerequisiteModules(ReusablePrerequisiteModules &&) = delete;
ReusablePrerequisiteModules
operator=(ReusablePrerequisiteModules &&) = delete;

~ReusablePrerequisiteModules() override = default;

void adjustHeaderSearchOptions(HeaderSearchOptions &Options) const override {
// Appending all built module files.
for (const auto &RequiredModule : RequiredModules)
Options.PrebuiltModuleFiles.insert_or_assign(
RequiredModule->getModuleName().str(),
RequiredModule->getModuleFilePath().str());
}

bool canReuse(const CompilerInvocation &CI,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem>) const override;

bool isModuleUnitBuilt(llvm::StringRef ModuleName) const {
return BuiltModuleNames.contains(ModuleName);
}

void addModuleFile(std::shared_ptr<const ModuleFile> ModuleFile) {
BuiltModuleNames.insert(ModuleFile->getModuleName());
RequiredModules.emplace_back(std::move(ModuleFile));
}

private:
llvm::SmallVector<std::shared_ptr<const ModuleFile>, 8> RequiredModules;
// A helper class to speedup the query if a module is built.
llvm::StringSet<> BuiltModuleNames;
};

bool IsModuleFileUpToDate(PathRef ModuleFilePath,
const PrerequisiteModules &RequisiteModules,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) {
Expand All @@ -138,7 +188,8 @@ bool IsModuleFileUpToDate(PathRef ModuleFilePath,

clang::clangd::IgnoreDiagnostics IgnoreDiags;
IntrusiveRefCntPtr<DiagnosticsEngine> Diags =
CompilerInstance::createDiagnostics(new DiagnosticOptions, &IgnoreDiags,
CompilerInstance::createDiagnostics(*VFS, new DiagnosticOptions,
&IgnoreDiags,
/*ShouldOwnClient=*/false);

LangOptions LangOpts;
Expand Down Expand Up @@ -192,89 +243,20 @@ bool IsModuleFilesUpToDate(
});
}

// StandalonePrerequisiteModules - stands for PrerequisiteModules for which all
// the required modules are built successfully. All the module files
// are owned by the StandalonePrerequisiteModules class.
//
// Any of the built module files won't be shared with other instances of the
// class. So that we can avoid worrying thread safety.
//
// We don't need to worry about duplicated module names here since the standard
// guarantees the module names should be unique to a program.
class StandalonePrerequisiteModules : public PrerequisiteModules {
public:
StandalonePrerequisiteModules() = default;

StandalonePrerequisiteModules(const StandalonePrerequisiteModules &) = delete;
StandalonePrerequisiteModules
operator=(const StandalonePrerequisiteModules &) = delete;
StandalonePrerequisiteModules(StandalonePrerequisiteModules &&) = delete;
StandalonePrerequisiteModules
operator=(StandalonePrerequisiteModules &&) = delete;

~StandalonePrerequisiteModules() override = default;

void adjustHeaderSearchOptions(HeaderSearchOptions &Options) const override {
// Appending all built module files.
for (auto &RequiredModule : RequiredModules)
Options.PrebuiltModuleFiles.insert_or_assign(
RequiredModule.ModuleName, RequiredModule.ModuleFilePath);
}

bool canReuse(const CompilerInvocation &CI,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem>) const override;

bool isModuleUnitBuilt(llvm::StringRef ModuleName) const {
return BuiltModuleNames.contains(ModuleName);
}

void addModuleFile(llvm::StringRef ModuleName,
llvm::StringRef ModuleFilePath) {
RequiredModules.emplace_back(ModuleName, ModuleFilePath);
BuiltModuleNames.insert(ModuleName);
}

private:
llvm::SmallVector<ModuleFile, 8> RequiredModules;
// A helper class to speedup the query if a module is built.
llvm::StringSet<> BuiltModuleNames;
};

// Build a module file for module with `ModuleName`. The information of built
// module file are stored in \param BuiltModuleFiles.
llvm::Error buildModuleFile(llvm::StringRef ModuleName,
const GlobalCompilationDatabase &CDB,
const ThreadsafeFS &TFS, ProjectModules &MDB,
PathRef ModuleFilesPrefix,
StandalonePrerequisiteModules &BuiltModuleFiles) {
if (BuiltModuleFiles.isModuleUnitBuilt(ModuleName))
return llvm::Error::success();

PathRef ModuleUnitFileName = MDB.getSourceForModuleName(ModuleName);
// It is possible that we're meeting third party modules (modules whose
// source are not in the project. e.g, the std module may be a third-party
// module for most projects) or something wrong with the implementation of
// ProjectModules.
// FIXME: How should we treat third party modules here? If we want to ignore
// third party modules, we should return true instead of false here.
// Currently we simply bail out.
if (ModuleUnitFileName.empty())
return llvm::createStringError("Failed to get the primary source");

/// Build a module file for module with `ModuleName`. The information of built
/// module file are stored in \param BuiltModuleFiles.
llvm::Expected<ModuleFile>
buildModuleFile(llvm::StringRef ModuleName, PathRef ModuleUnitFileName,
const GlobalCompilationDatabase &CDB, const ThreadsafeFS &TFS,
const ReusablePrerequisiteModules &BuiltModuleFiles) {
// Try cheap operation earlier to boil-out cheaply if there are problems.
auto Cmd = CDB.getCompileCommand(ModuleUnitFileName);
if (!Cmd)
return llvm::createStringError(
llvm::formatv("No compile command for {0}", ModuleUnitFileName));

for (auto &RequiredModuleName : MDB.getRequiredModules(ModuleUnitFileName)) {
// Return early if there are errors building the module file.
if (llvm::Error Err = buildModuleFile(RequiredModuleName, CDB, TFS, MDB,
ModuleFilesPrefix, BuiltModuleFiles))
return llvm::createStringError(
llvm::formatv("Failed to build dependency {0}: {1}",
RequiredModuleName, llvm::toString(std::move(Err))));
}
llvm::SmallString<256> ModuleFilesPrefix =
getUniqueModuleFilesPath(ModuleUnitFileName);

Cmd->Output = getModuleFilePath(ModuleName, ModuleFilesPrefix);

Expand Down Expand Up @@ -316,58 +298,186 @@ llvm::Error buildModuleFile(llvm::StringRef ModuleName,
if (Clang->getDiagnostics().hasErrorOccurred())
return llvm::createStringError("Compilation failed");

BuiltModuleFiles.addModuleFile(ModuleName, Inputs.CompileCommand.Output);
return llvm::Error::success();
return ModuleFile{ModuleName, Inputs.CompileCommand.Output};
}

bool ReusablePrerequisiteModules::canReuse(
const CompilerInvocation &CI,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) const {
if (RequiredModules.empty())
return true;

llvm::SmallVector<llvm::StringRef> BMIPaths;
for (auto &MF : RequiredModules)
BMIPaths.push_back(MF->getModuleFilePath());
return IsModuleFilesUpToDate(BMIPaths, *this, VFS);
}

class ModuleFileCache {
public:
ModuleFileCache(const GlobalCompilationDatabase &CDB) : CDB(CDB) {}
const GlobalCompilationDatabase &getCDB() const { return CDB; }

std::shared_ptr<const ModuleFile> getModule(StringRef ModuleName);

void add(StringRef ModuleName, std::shared_ptr<const ModuleFile> ModuleFile) {
std::lock_guard<std::mutex> Lock(ModuleFilesMutex);

ModuleFiles[ModuleName] = ModuleFile;
}

void remove(StringRef ModuleName);

private:
const GlobalCompilationDatabase &CDB;

llvm::StringMap<std::weak_ptr<const ModuleFile>> ModuleFiles;
// Mutex to guard accesses to ModuleFiles.
std::mutex ModuleFilesMutex;
};

std::shared_ptr<const ModuleFile>
ModuleFileCache::getModule(StringRef ModuleName) {
std::lock_guard<std::mutex> Lock(ModuleFilesMutex);

auto Iter = ModuleFiles.find(ModuleName);
if (Iter == ModuleFiles.end())
return nullptr;

if (auto Res = Iter->second.lock())
return Res;

ModuleFiles.erase(Iter);
return nullptr;
}

void ModuleFileCache::remove(StringRef ModuleName) {
std::lock_guard<std::mutex> Lock(ModuleFilesMutex);

ModuleFiles.erase(ModuleName);
}

/// Collect the directly and indirectly required module names for \param
/// ModuleName in topological order. The \param ModuleName is guaranteed to
/// be the last element in \param ModuleNames.
llvm::SmallVector<StringRef> getAllRequiredModules(ProjectModules &MDB,
StringRef ModuleName) {
llvm::SmallVector<llvm::StringRef> ModuleNames;
llvm::StringSet<> ModuleNamesSet;

auto VisitDeps = [&](StringRef ModuleName, auto Visitor) -> void {
ModuleNamesSet.insert(ModuleName);

for (StringRef RequiredModuleName :
MDB.getRequiredModules(MDB.getSourceForModuleName(ModuleName)))
if (ModuleNamesSet.insert(RequiredModuleName).second)
Visitor(RequiredModuleName, Visitor);

ModuleNames.push_back(ModuleName);
};
VisitDeps(ModuleName, VisitDeps);

return ModuleNames;
}

} // namespace

class ModulesBuilder::ModulesBuilderImpl {
public:
ModulesBuilderImpl(const GlobalCompilationDatabase &CDB) : Cache(CDB) {}

const GlobalCompilationDatabase &getCDB() const { return Cache.getCDB(); }

llvm::Error
getOrBuildModuleFile(StringRef ModuleName, const ThreadsafeFS &TFS,
ProjectModules &MDB,
ReusablePrerequisiteModules &BuiltModuleFiles);

private:
ModuleFileCache Cache;
};

llvm::Error ModulesBuilder::ModulesBuilderImpl::getOrBuildModuleFile(
StringRef ModuleName, const ThreadsafeFS &TFS, ProjectModules &MDB,
ReusablePrerequisiteModules &BuiltModuleFiles) {
if (BuiltModuleFiles.isModuleUnitBuilt(ModuleName))
return llvm::Error::success();

PathRef ModuleUnitFileName = MDB.getSourceForModuleName(ModuleName);
/// It is possible that we're meeting third party modules (modules whose
/// source are not in the project. e.g, the std module may be a third-party
/// module for most project) or something wrong with the implementation of
/// ProjectModules.
/// FIXME: How should we treat third party modules here? If we want to ignore
/// third party modules, we should return true instead of false here.
/// Currently we simply bail out.
if (ModuleUnitFileName.empty())
return llvm::createStringError(
llvm::formatv("Don't get the module unit for module {0}", ModuleName));

// Get Required modules in topological order.
auto ReqModuleNames = getAllRequiredModules(MDB, ModuleName);
for (llvm::StringRef ReqModuleName : ReqModuleNames) {
if (BuiltModuleFiles.isModuleUnitBuilt(ModuleName))
continue;

if (auto Cached = Cache.getModule(ReqModuleName)) {
if (IsModuleFileUpToDate(Cached->getModuleFilePath(), BuiltModuleFiles,
TFS.view(std::nullopt))) {
log("Reusing module {0} from {1}", ModuleName,
Cached->getModuleFilePath());
BuiltModuleFiles.addModuleFile(std::move(Cached));
continue;
}
Cache.remove(ReqModuleName);
}

llvm::Expected<ModuleFile> MF = buildModuleFile(
ModuleName, ModuleUnitFileName, getCDB(), TFS, BuiltModuleFiles);
if (llvm::Error Err = MF.takeError())
return Err;

log("Built module {0} to {1}", ModuleName, MF->getModuleFilePath());
auto BuiltModuleFile = std::make_shared<const ModuleFile>(std::move(*MF));
Cache.add(ModuleName, BuiltModuleFile);
BuiltModuleFiles.addModuleFile(std::move(BuiltModuleFile));
}

return llvm::Error::success();
}

std::unique_ptr<PrerequisiteModules>
ModulesBuilder::buildPrerequisiteModulesFor(PathRef File,
const ThreadsafeFS &TFS) const {
std::unique_ptr<ProjectModules> MDB = CDB.getProjectModules(File);
const ThreadsafeFS &TFS) {
std::unique_ptr<ProjectModules> MDB = Impl->getCDB().getProjectModules(File);
if (!MDB) {
elog("Failed to get Project Modules information for {0}", File);
return std::make_unique<FailedPrerequisiteModules>();
}

std::vector<std::string> RequiredModuleNames = MDB->getRequiredModules(File);
if (RequiredModuleNames.empty())
return std::make_unique<StandalonePrerequisiteModules>();

llvm::SmallString<256> ModuleFilesPrefix = getUniqueModuleFilesPath(File);

log("Trying to build required modules for {0} in {1}", File,
ModuleFilesPrefix);

auto RequiredModules = std::make_unique<StandalonePrerequisiteModules>();
return std::make_unique<ReusablePrerequisiteModules>();

auto RequiredModules = std::make_unique<ReusablePrerequisiteModules>();
for (llvm::StringRef RequiredModuleName : RequiredModuleNames) {
// Return early if there is any error.
if (llvm::Error Err =
buildModuleFile(RequiredModuleName, CDB, TFS, *MDB.get(),
ModuleFilesPrefix, *RequiredModules.get())) {
if (llvm::Error Err = Impl->getOrBuildModuleFile(
RequiredModuleName, TFS, *MDB.get(), *RequiredModules.get())) {
elog("Failed to build module {0}; due to {1}", RequiredModuleName,
toString(std::move(Err)));
return std::make_unique<FailedPrerequisiteModules>();
}
}

log("Built required modules for {0} in {1}", File, ModuleFilesPrefix);

return std::move(RequiredModules);
}

bool StandalonePrerequisiteModules::canReuse(
const CompilerInvocation &CI,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) const {
if (RequiredModules.empty())
return true;

SmallVector<StringRef> BMIPaths;
for (auto &MF : RequiredModules)
BMIPaths.push_back(MF.ModuleFilePath);
return IsModuleFilesUpToDate(BMIPaths, *this, VFS);
ModulesBuilder::ModulesBuilder(const GlobalCompilationDatabase &CDB) {
Impl = std::make_unique<ModulesBuilderImpl>(CDB);
}

ModulesBuilder::~ModulesBuilder() {}

} // namespace clangd
} // namespace clang
8 changes: 5 additions & 3 deletions clang-tools-extra/clangd/ModulesBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ class PrerequisiteModules {
/// different versions and different source files.
class ModulesBuilder {
public:
ModulesBuilder(const GlobalCompilationDatabase &CDB) : CDB(CDB) {}
ModulesBuilder(const GlobalCompilationDatabase &CDB);
~ModulesBuilder();

ModulesBuilder(const ModulesBuilder &) = delete;
ModulesBuilder(ModulesBuilder &&) = delete;
Expand All @@ -94,10 +95,11 @@ class ModulesBuilder {
ModulesBuilder &operator=(ModulesBuilder &&) = delete;

std::unique_ptr<PrerequisiteModules>
buildPrerequisiteModulesFor(PathRef File, const ThreadsafeFS &TFS) const;
buildPrerequisiteModulesFor(PathRef File, const ThreadsafeFS &TFS);

private:
const GlobalCompilationDatabase &CDB;
class ModulesBuilderImpl;
std::unique_ptr<ModulesBuilderImpl> Impl;
};

} // namespace clangd
Expand Down
4 changes: 2 additions & 2 deletions clang-tools-extra/clangd/Preamble.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -613,8 +613,9 @@ buildPreamble(PathRef FileName, CompilerInvocation CI,
for (const auto &L : ASTListeners)
L->sawDiagnostic(D, Diag);
});
auto VFS = Inputs.TFS->view(Inputs.CompileCommand.Directory);
llvm::IntrusiveRefCntPtr<DiagnosticsEngine> PreambleDiagsEngine =
CompilerInstance::createDiagnostics(&CI.getDiagnosticOpts(),
CompilerInstance::createDiagnostics(*VFS, &CI.getDiagnosticOpts(),
&PreambleDiagnostics,
/*ShouldOwnClient=*/false);
const Config &Cfg = Config::current();
Expand Down Expand Up @@ -651,7 +652,6 @@ buildPreamble(PathRef FileName, CompilerInvocation CI,
for (const auto &L : ASTListeners)
L->beforeExecute(CI);
});
auto VFS = Inputs.TFS->view(Inputs.CompileCommand.Directory);
llvm::SmallString<32> AbsFileName(FileName);
VFS->makeAbsolute(AbsFileName);
auto StatCache = std::make_shared<PreambleFileStatusCache>(AbsFileName);
Expand Down
29 changes: 29 additions & 0 deletions clang-tools-extra/clangd/Protocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,35 @@ bool fromJSON(const llvm::json::Value &Params, ClientCapabilities &R,
if (auto EditsNearCursor = Completion->getBoolean("editsNearCursor"))
R.CompletionFixes |= *EditsNearCursor;
}
if (auto *References = TextDocument->getObject("references")) {
if (auto ContainerSupport = References->getBoolean("container")) {
R.ReferenceContainer |= *ContainerSupport;
}
}
if (auto *Diagnostics = TextDocument->getObject("publishDiagnostics")) {
if (auto CodeActions = Diagnostics->getBoolean("codeActionsInline")) {
R.DiagnosticFixes |= *CodeActions;
}
}
if (auto *InactiveRegions =
TextDocument->getObject("inactiveRegionsCapabilities")) {
if (auto InactiveRegionsSupport =
InactiveRegions->getBoolean("inactiveRegions")) {
R.InactiveRegions |= *InactiveRegionsSupport;
}
}
}
if (auto *Window = Experimental->getObject("window")) {
if (auto Implicit =
Window->getBoolean("implicitWorkDoneProgressCreate")) {
R.ImplicitProgressCreation |= *Implicit;
}
}
if (auto *OffsetEncoding = Experimental->get("offsetEncoding")) {
R.offsetEncoding.emplace();
if (!fromJSON(*OffsetEncoding, *R.offsetEncoding,
P.field("offsetEncoding")))
return false;
}
}

Expand Down
3 changes: 3 additions & 0 deletions clang-tools-extra/clangd/Protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,7 @@ struct ClientCapabilities {
std::optional<SymbolKindBitset> WorkspaceSymbolKinds;

/// Whether the client accepts diagnostics with codeActions attached inline.
/// This is a clangd extension.
/// textDocument.publishDiagnostics.codeActionsInline.
bool DiagnosticFixes = false;

Expand All @@ -475,6 +476,7 @@ struct ClientCapabilities {

/// Client supports displaying a container string for results of
/// textDocument/reference (clangd extension)
/// textDocument.references.container
bool ReferenceContainer = false;

/// Client supports hierarchical document symbols.
Expand Down Expand Up @@ -563,6 +565,7 @@ struct ClientCapabilities {

/// Whether the client supports the textDocument/inactiveRegions
/// notification. This is a clangd extension.
/// textDocument.inactiveRegionsCapabilities.inactiveRegions
bool InactiveRegions = false;
};
bool fromJSON(const llvm::json::Value &, ClientCapabilities &,
Expand Down
21 changes: 17 additions & 4 deletions clang-tools-extra/clangd/XRefs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"
#include <optional>
Expand Down Expand Up @@ -2275,7 +2276,7 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
// Initially store the ranges in a map keyed by SymbolID of the caller.
// This allows us to group different calls with the same caller
// into the same CallHierarchyIncomingCall.
llvm::DenseMap<SymbolID, std::vector<Range>> CallsIn;
llvm::DenseMap<SymbolID, std::vector<Location>> CallsIn;
// We can populate the ranges based on a refs request only. As we do so, we
// also accumulate the container IDs into a lookup request.
LookupRequest ContainerLookup;
Expand All @@ -2285,7 +2286,7 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
elog("incomingCalls failed to convert location: {0}", Loc.takeError());
return;
}
CallsIn[R.Container].push_back(Loc->range);
CallsIn[R.Container].push_back(*Loc);

ContainerLookup.IDs.insert(R.Container);
});
Expand All @@ -2294,9 +2295,21 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
Index->lookup(ContainerLookup, [&](const Symbol &Caller) {
auto It = CallsIn.find(Caller.ID);
assert(It != CallsIn.end());
if (auto CHI = symbolToCallHierarchyItem(Caller, Item.uri.file()))
if (auto CHI = symbolToCallHierarchyItem(Caller, Item.uri.file())) {
std::vector<Range> FromRanges;
for (const Location &L : It->second) {
if (L.uri != CHI->uri) {
// Call location not in same file as caller.
// This can happen in some edge cases. There's not much we can do,
// since the protocol only allows returning ranges interpreted as
// being in the caller's file.
continue;
}
FromRanges.push_back(L.range);
}
Results.push_back(
CallHierarchyIncomingCall{std::move(*CHI), std::move(It->second)});
CallHierarchyIncomingCall{std::move(*CHI), std::move(FromRanges)});
}
});
// Sort results by name of container.
llvm::sort(Results, [](const CallHierarchyIncomingCall &A,
Expand Down
60 changes: 41 additions & 19 deletions clang-tools-extra/clangd/refactor/tweaks/DefineOutline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,13 @@ findContextForNS(llvm::StringRef TargetNS, const DeclContext *CurContext) {
// afterwards it can be shared with define-inline code action.
llvm::Expected<std::string>
getFunctionSourceAfterReplacements(const FunctionDecl *FD,
const tooling::Replacements &Replacements) {
const tooling::Replacements &Replacements,
bool TargetFileIsHeader) {
const auto &SM = FD->getASTContext().getSourceManager();
auto OrigFuncRange = toHalfOpenFileRange(
SM, FD->getASTContext().getLangOpts(), FD->getSourceRange());
if (!OrigFuncRange)
return error("Couldn't get range for function.");
assert(!FD->getDescribedFunctionTemplate() &&
"Define out-of-line doesn't apply to function templates.");

// Get new begin and end positions for the qualified function definition.
unsigned FuncBegin = SM.getFileOffset(OrigFuncRange->getBegin());
Expand All @@ -129,24 +128,38 @@ getFunctionSourceAfterReplacements(const FunctionDecl *FD,
if (!QualifiedFunc)
return QualifiedFunc.takeError();

auto Source = QualifiedFunc->substr(FuncBegin, FuncEnd - FuncBegin + 1);
std::string TemplatePrefix;
auto AddToTemplatePrefixIfApplicable = [&](const Decl *D) {
const TemplateParameterList *Params = D->getDescribedTemplateParams();
if (!Params)
return;
for (Decl *P : *Params) {
if (auto *TTP = dyn_cast<TemplateTypeParmDecl>(P))
TTP->removeDefaultArgument();
else if (auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(P))
NTTP->removeDefaultArgument();
else if (auto *TTPD = dyn_cast<TemplateTemplateParmDecl>(P))
TTPD->removeDefaultArgument();
}
std::string S;
llvm::raw_string_ostream Stream(S);
Params->print(Stream, FD->getASTContext());
if (!S.empty())
*S.rbegin() = '\n'; // Replace space with newline
TemplatePrefix.insert(0, S);
};
AddToTemplatePrefixIfApplicable(FD);
if (auto *MD = llvm::dyn_cast<CXXMethodDecl>(FD)) {
for (const CXXRecordDecl *Parent = MD->getParent(); Parent;
Parent =
llvm::dyn_cast_or_null<const CXXRecordDecl>(Parent->getParent())) {
if (const TemplateParameterList *Params =
Parent->getDescribedTemplateParams()) {
std::string S;
llvm::raw_string_ostream Stream(S);
Params->print(Stream, FD->getASTContext());
if (!S.empty())
*S.rbegin() = '\n'; // Replace space with newline
TemplatePrefix.insert(0, S);
}
AddToTemplatePrefixIfApplicable(Parent);
}
}

auto Source = QualifiedFunc->substr(FuncBegin, FuncEnd - FuncBegin + 1);
if (TargetFileIsHeader)
Source.insert(0, "inline ");
if (!TemplatePrefix.empty())
Source.insert(0, TemplatePrefix);
return Source;
Expand Down Expand Up @@ -202,7 +215,8 @@ deleteTokensWithKind(const syntax::TokenBuffer &TokBuf, tok::TokenKind Kind,
llvm::Expected<std::string>
getFunctionSourceCode(const FunctionDecl *FD, const DeclContext *TargetContext,
const syntax::TokenBuffer &TokBuf,
const HeuristicResolver *Resolver) {
const HeuristicResolver *Resolver,
bool TargetFileIsHeader) {
auto &AST = FD->getASTContext();
auto &SM = AST.getSourceManager();

Expand All @@ -225,6 +239,8 @@ getFunctionSourceCode(const FunctionDecl *FD, const DeclContext *TargetContext,
return;

for (const NamedDecl *ND : Ref.Targets) {
if (ND->getKind() == Decl::TemplateTypeParm)
return;
if (ND->getDeclContext() != Ref.Targets.front()->getDeclContext()) {
elog("Targets from multiple contexts: {0}, {1}",
printQualifiedName(*Ref.Targets.front()),
Expand Down Expand Up @@ -337,7 +353,8 @@ getFunctionSourceCode(const FunctionDecl *FD, const DeclContext *TargetContext,

if (Errors)
return std::move(Errors);
return getFunctionSourceAfterReplacements(FD, DeclarationCleanups);
return getFunctionSourceAfterReplacements(FD, DeclarationCleanups,
TargetFileIsHeader);
}

struct InsertionPoint {
Expand Down Expand Up @@ -419,15 +436,15 @@ class DefineOutline : public Tweak {
Source->isOutOfLine())
return false;

// Bail out if this is a function template or specialization, as their
// Bail out if this is a function template specialization, as their
// definitions need to be visible in all including translation units.
if (Source->getDescribedFunctionTemplate())
return false;
if (Source->getTemplateSpecializationInfo())
return false;

auto *MD = llvm::dyn_cast<CXXMethodDecl>(Source);
if (!MD) {
if (Source->getDescribedFunctionTemplate())
return false;
// Can't outline free-standing functions in the same file.
return !SameFile;
}
Expand All @@ -450,6 +467,10 @@ class DefineOutline : public Tweak {
}
}

// Function templates must be defined in the same file.
if (MD->getDescribedTemplate())
SameFile = true;

// The refactoring is meaningless for unnamed classes and namespaces,
// unless we're outlining in the same file
for (const DeclContext *DC = MD->getParent(); DC; DC = DC->getParent()) {
Expand Down Expand Up @@ -485,7 +506,8 @@ class DefineOutline : public Tweak {

auto FuncDef = getFunctionSourceCode(
Source, InsertionPoint->EnclosingNamespace, Sel.AST->getTokens(),
Sel.AST->getHeuristicResolver());
Sel.AST->getHeuristicResolver(),
SameFile && isHeaderFile(Sel.AST->tuPath(), Sel.AST->getLangOpts()));
if (!FuncDef)
return FuncDef.takeError();

Expand Down
19 changes: 11 additions & 8 deletions clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/NestedNameSpecifier.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/Stmt.h"
Expand All @@ -70,7 +71,6 @@
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/raw_os_ostream.h"
#include <optional>

namespace clang {
Expand All @@ -95,18 +95,21 @@ enum FunctionDeclKind {
OutOfLineDefinition
};

// A RootStmt is a statement that's fully selected including all it's children
// and it's parent is unselected.
// A RootStmt is a statement that's fully selected including all its children
// and its parent is unselected.
// Check if a node is a root statement.
bool isRootStmt(const Node *N) {
if (!N->ASTNode.get<Stmt>())
return false;
// Root statement cannot be partially selected.
if (N->Selected == SelectionTree::Partial)
return false;
// Only DeclStmt can be an unselected RootStmt since VarDecls claim the entire
// selection range in selectionTree.
if (N->Selected == SelectionTree::Unselected && !N->ASTNode.get<DeclStmt>())
// A DeclStmt can be an unselected RootStmt since VarDecls claim the entire
// selection range in selectionTree. Additionally, a CXXOperatorCallExpr of a
// binary operation can be unselected because its children claim the entire
// selection range in the selection tree (e.g. <<).
if (N->Selected == SelectionTree::Unselected && !N->ASTNode.get<DeclStmt>() &&
!N->ASTNode.get<CXXOperatorCallExpr>())
return false;
return true;
}
Expand Down Expand Up @@ -913,8 +916,8 @@ Expected<Tweak::Effect> ExtractFunction::apply(const Selection &Inputs) {

tooling::Replacements OtherEdit(
createForwardDeclaration(*ExtractedFunc, SM));
if (auto PathAndEdit = Tweak::Effect::fileEdit(SM, SM.getFileID(*FwdLoc),
OtherEdit))
if (auto PathAndEdit =
Tweak::Effect::fileEdit(SM, SM.getFileID(*FwdLoc), OtherEdit))
MultiFileEffect->ApplyEdits.try_emplace(PathAndEdit->first,
PathAndEdit->second);
else
Expand Down
29 changes: 29 additions & 0 deletions clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,35 @@ TEST(CallHierarchy, HierarchyOnVar) {
fromRanges(Source.range("Callee")))));
}

TEST(CallHierarchy, CallInDifferentFileThanCaller) {
Annotations Header(R"cpp(
#define WALDO void caller() {
)cpp");
Annotations Source(R"cpp(
void call^ee();
WALDO
callee();
}
)cpp");
auto TU = TestTU::withCode(Source.code());
TU.HeaderCode = Header.code();
auto AST = TU.build();
auto Index = TU.index();

std::vector<CallHierarchyItem> Items =
prepareCallHierarchy(AST, Source.point(), testPath(TU.Filename));
ASSERT_THAT(Items, ElementsAre(withName("callee")));

auto Incoming = incomingCalls(Items[0], Index.get());

// The only call site is in the source file, which is a different file from
// the declaration of the function containing the call, which is in the
// header. The protocol does not allow us to represent such calls, so we drop
// them. (The call hierarchy item itself is kept.)
EXPECT_THAT(Incoming,
ElementsAre(AllOf(from(withName("caller")), fromRanges())));
}

} // namespace
} // namespace clangd
} // namespace clang
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,13 @@ TEST_F(OverlayCDBTest, GetCompileCommand) {
EXPECT_EQ(CDB.getCompileCommand(testPath("missing.cc")), std::nullopt);

auto Override = cmd(testPath("foo.cc"), "-DA=3");
CDB.setCompileCommand(testPath("foo.cc"), Override);
EXPECT_TRUE(CDB.setCompileCommand(testPath("foo.cc"), Override));
EXPECT_FALSE(CDB.setCompileCommand(testPath("foo.cc"), Override));
EXPECT_THAT(CDB.getCompileCommand(testPath("foo.cc"))->CommandLine,
Contains("-DA=3"));
EXPECT_EQ(CDB.getCompileCommand(testPath("missing.cc")), std::nullopt);
CDB.setCompileCommand(testPath("missing.cc"), Override);
EXPECT_TRUE(CDB.setCompileCommand(testPath("missing.cc"), Override));
EXPECT_FALSE(CDB.setCompileCommand(testPath("missing.cc"), Override));
EXPECT_THAT(CDB.getCompileCommand(testPath("missing.cc"))->CommandLine,
Contains("-DA=3"));
}
Expand All @@ -111,7 +113,7 @@ TEST_F(OverlayCDBTest, NoBase) {
OverlayCDB CDB(nullptr, {"-DA=6"});
EXPECT_EQ(CDB.getCompileCommand(testPath("bar.cc")), std::nullopt);
auto Override = cmd(testPath("bar.cc"), "-DA=5");
CDB.setCompileCommand(testPath("bar.cc"), Override);
EXPECT_TRUE(CDB.setCompileCommand(testPath("bar.cc"), Override));
EXPECT_THAT(CDB.getCompileCommand(testPath("bar.cc"))->CommandLine,
Contains("-DA=5"));

Expand All @@ -128,10 +130,10 @@ TEST_F(OverlayCDBTest, Watch) {
Changes.push_back(ChangedFiles);
});

Inner.setCompileCommand("A.cpp", tooling::CompileCommand());
Outer.setCompileCommand("B.cpp", tooling::CompileCommand());
Inner.setCompileCommand("A.cpp", std::nullopt);
Outer.setCompileCommand("C.cpp", std::nullopt);
EXPECT_TRUE(Inner.setCompileCommand("A.cpp", tooling::CompileCommand()));
EXPECT_TRUE(Outer.setCompileCommand("B.cpp", tooling::CompileCommand()));
EXPECT_TRUE(Inner.setCompileCommand("A.cpp", std::nullopt));
EXPECT_TRUE(Outer.setCompileCommand("C.cpp", std::nullopt));
EXPECT_THAT(Changes, ElementsAre(ElementsAre("A.cpp"), ElementsAre("B.cpp"),
ElementsAre("A.cpp"), ElementsAre("C.cpp")));
}
Expand All @@ -151,7 +153,7 @@ TEST_F(OverlayCDBTest, Adjustments) {
tooling::CompileCommand BarCommand;
BarCommand.Filename = testPath("bar.cc");
BarCommand.CommandLine = {"clang++", "-DB=1", testPath("bar.cc")};
CDB.setCompileCommand(testPath("bar.cc"), BarCommand);
EXPECT_TRUE(CDB.setCompileCommand(testPath("bar.cc"), BarCommand));
Cmd = *CDB.getCompileCommand(testPath("bar.cc"));
EXPECT_THAT(
Cmd.CommandLine,
Expand Down Expand Up @@ -412,7 +414,7 @@ TEST(GlobalCompilationDatabaseTest, NonCanonicalFilenames) {

llvm::SmallString<128> Root(testRoot());
llvm::sys::path::append(Root, "build", "..", "a.cc");
DB.setCompileCommand(Root.str(), tooling::CompileCommand());
EXPECT_TRUE(DB.setCompileCommand(Root.str(), tooling::CompileCommand()));
EXPECT_THAT(DiscoveredFiles, UnorderedElementsAre(testPath("a.cc")));
DiscoveredFiles.clear();

Expand All @@ -432,7 +434,7 @@ TEST_F(OverlayCDBTest, GetProjectInfo) {
EXPECT_EQ(DB.getProjectInfo(Header)->SourceRoot, testRoot());

// Shouldn't change after an override.
DB.setCompileCommand(File, tooling::CompileCommand());
EXPECT_TRUE(DB.setCompileCommand(File, tooling::CompileCommand()));
EXPECT_EQ(DB.getProjectInfo(File)->SourceRoot, testRoot());
EXPECT_EQ(DB.getProjectInfo(Header)->SourceRoot, testRoot());
}
Expand Down
16 changes: 16 additions & 0 deletions clang-tools-extra/clangd/unittests/InlayHintTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1576,6 +1576,22 @@ TEST(TypeHints, Aliased) {
EXPECT_THAT(hintsOfKind(AST, InlayHintKind::Type), IsEmpty());
}

TEST(TypeHints, CallingConvention) {
// Check that we don't crash for lambdas without a FunctionTypeLoc
// https://github.com/clangd/clangd/issues/2223
std::string Code = R"cpp(
void test() {
[]() __cdecl {};
}
)cpp";
TestTU TU = TestTU::withCode(Code);
TU.ExtraArgs.push_back("--target=x86_64-w64-mingw32");
TU.PredefineMacros = true; // for the __cdecl
auto AST = TU.build();

EXPECT_THAT(hintsOfKind(AST, InlayHintKind::Type), IsEmpty());
}

TEST(TypeHints, Decltype) {
assertTypeHints(R"cpp(
$a[[decltype(0)]] a;
Expand Down
Loading