49 changes: 49 additions & 0 deletions bolt/test/AArch64/ifunc.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// With -O0 indirect call is performed on IPLT trampoline. IPLT trampoline
// has IFUNC symbol.
// RUN: %clang %cflags -nostdlib -O0 -no-pie %p/../Inputs/ifunc.c -fuse-ld=lld \
// RUN: -o %t.O0.exe -Wl,-q
// RUN: llvm-bolt %t.O0.exe -o %t.O0.bolt.exe \
// RUN: --print-disasm --print-only=_start | \
// RUN: FileCheck --check-prefix=CHECK %s
// RUN: llvm-readelf -aW %t.O0.bolt.exe | \
// RUN: FileCheck --check-prefix=REL_CHECK %s

// Non-pie static executable doesn't generate PT_DYNAMIC, check relocation
// is readed successfully and IPLT trampoline has been identified by bolt.
// RUN: %clang %cflags -nostdlib -O3 %p/../Inputs/ifunc.c -fuse-ld=lld -no-pie \
// RUN: -o %t.O3_nopie.exe -Wl,-q
// RUN: llvm-readelf -l %t.O3_nopie.exe | \
// RUN: FileCheck --check-prefix=NON_DYN_CHECK %s
// RUN: llvm-bolt %t.O3_nopie.exe -o %t.O3_nopie.bolt.exe \
// RUN: --print-disasm --print-only=_start | \
// RUN: FileCheck --check-prefix=CHECK %s
// RUN: llvm-readelf -aW %t.O3_nopie.bolt.exe | \
// RUN: FileCheck --check-prefix=REL_CHECK %s

// With -O3 direct call is performed on IPLT trampoline. IPLT trampoline
// doesn't have associated symbol. The ifunc symbol has the same address as
// IFUNC resolver function.
// RUN: %clang %cflags -nostdlib -O3 %p/../Inputs/ifunc.c -fuse-ld=lld -fPIC -pie \
// RUN: -o %t.O3_pie.exe -Wl,-q
// RUN: llvm-bolt %t.O3_pie.exe -o %t.O3_pie.bolt.exe \
// RUN: --print-disasm --print-only=_start | \
// RUN: FileCheck --check-prefix=CHECK %s
// RUN: llvm-readelf -aW %t.O3_pie.bolt.exe | \
// RUN: FileCheck --check-prefix=REL_CHECK %s

// Check that IPLT trampoline located in .plt section are normally handled by
// BOLT. The gnu-ld linker doesn't use separate .iplt section.
// RUN: %clang %cflags -nostdlib -O3 %p/../Inputs/ifunc.c -fuse-ld=lld -fPIC -pie \
// RUN: -T %p/../Inputs/iplt.ld -o %t.iplt_O3_pie.exe -Wl,-q
// RUN: llvm-bolt %t.iplt_O3_pie.exe -o %t.iplt_O3_pie.bolt.exe \
// RUN: --print-disasm --print-only=_start | \
// RUN: FileCheck --check-prefix=CHECK %s
// RUN: llvm-readelf -aW %t.iplt_O3_pie.bolt.exe | \
// RUN: FileCheck --check-prefix=REL_CHECK %s

// NON_DYN_CHECK-NOT: DYNAMIC

// CHECK: {{(bl? "(resolver_foo|ifoo).*@PLT"|adr x[0-9]+, ifoo)}}

// REL_CHECK: R_AARCH64_IRELATIVE [[#%x,REL_SYMB_ADDR:]]
// REL_CHECK: [[#REL_SYMB_ADDR]] {{.*}} FUNC {{.*}} resolver_foo
2 changes: 1 addition & 1 deletion bolt/test/AArch64/update-weak-reference-symbol.s
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// RUN: %clang %cflags -Wl,-z,notext -shared -Wl,-q %s -o %t.so
// RUN: llvm-bolt %t.so -o %t.so.bolt
// RUN: llvm-nm -n %t.so.bolt > %t.out.txt
// RUN: llvm-objdump -dj .rodata %t.so.bolt >> %t.out.txt
// RUN: llvm-objdump -z -dj .rodata %t.so.bolt >> %t.out.txt
// RUN: FileCheck %s --input-file=%t.out.txt

# CHECK: w func_1
Expand Down
12 changes: 12 additions & 0 deletions bolt/test/Inputs/ifunc.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// This test checks that IFUNC trampoline is properly recognised by BOLT

static void foo() {}
static void bar() {}

extern int use_foo;

static void *resolver_foo(void) { return use_foo ? foo : bar; }

__attribute__((ifunc("resolver_foo"))) void ifoo();

void _start() { ifoo(); }
File renamed without changes.
47 changes: 47 additions & 0 deletions bolt/test/X86/ifunc.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Check if BOLT can process ifunc symbols from .plt section
// RUN: %clang %cflags -nostdlib -no-pie %p/../Inputs/ifunc.c -fuse-ld=lld \
// RUN: -o %t.exe -Wl,-q
// RUN: llvm-bolt %t.exe -o %t.bolt.exe \
// RUN: --print-disasm --print-only=_start | \
// RUN: FileCheck --check-prefix=CHECK %s
// RUN: llvm-readelf -aW %t.bolt.exe | \
// RUN: FileCheck --check-prefix=REL_CHECK %s

// Check if BOLT can process ifunc symbols from .plt section in non-pie static
// executable case.
// RUN: %clang %cflags -nostdlib %p/../Inputs/ifunc.c -fuse-ld=lld -no-pie \
// RUN: -o %t.nopie.exe -Wl,-q
// RUN: llvm-readelf -l %t.nopie.exe | \
// RUN: FileCheck --check-prefix=NON_DYN_CHECK %s
// RUN: llvm-bolt %t.nopie.exe -o %t.nopie.bolt.exe \
// RUN: --print-disasm --print-only=_start | \
// RUN: FileCheck --check-prefix=CHECK %s
// RUN: llvm-readelf -aW %t.nopie.bolt.exe | \
// RUN: FileCheck --check-prefix=REL_CHECK %s

// Check if BOLT can process ifunc symbols from .plt section in pie executable
// case.
// RUN: %clang %cflags -nostdlib %p/../Inputs/ifunc.c -fuse-ld=lld -fPIC -pie \
// RUN: -o %t.pie.exe -Wl,-q
// RUN: llvm-bolt %t.pie.exe -o %t.pie.bolt.exe \
// RUN: --print-disasm --print-only=_start | \
// RUN: FileCheck --check-prefix=CHECK %s
// RUN: llvm-readelf -aW %t.pie.bolt.exe | \
// RUN: FileCheck --check-prefix=REL_CHECK %s

// Check that IPLT trampoline located in .plt section are normally handled by
// BOLT. The gnu-ld linker doesn't use separate .iplt section.
// RUN: %clang %cflags -nostdlib %p/../Inputs/ifunc.c -fuse-ld=lld -fPIC -pie \
// RUN: -T %p/../Inputs/iplt.ld -o %t.iplt_pie.exe -Wl,-q
// RUN: llvm-bolt %t.iplt_pie.exe -o %t.iplt_pie.bolt.exe \
// RUN: --print-disasm --print-only=_start | \
// RUN: FileCheck --check-prefix=CHECK %s
// RUN: llvm-readelf -aW %t.iplt_pie.bolt.exe | \
// RUN: FileCheck --check-prefix=REL_CHECK %s

// NON_DYN_CHECK-NOT: DYNAMIC

// CHECK: callq "resolver_foo/1@PLT"

// REL_CHECK: R_X86_64_IRELATIVE [[#%x,REL_SYMB_ADDR:]]
// REL_CHECK: [[#REL_SYMB_ADDR]] {{.*}} FUNC {{.*}} resolver_foo
4 changes: 2 additions & 2 deletions bolt/test/X86/log.test
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ RUN: yaml2obj %p/Inputs/blarge.yaml &> %t.exe
RUN: llvm-bolt %t.exe -o %t.null --data %p/Inputs/blarge.fdata -v=2 \
RUN: --reorder-blocks=normal --print-finalized --log-file=%t.log 2>&1 \
RUN: | FileCheck --check-prefix=CHECK --allow-empty %s
RUN: cat %t.log | FileCheck %s --check-prefix=CHECK-LOG
RUN: FileCheck %s --check-prefix=CHECK-LOG --input-file %t.log

CHECK-NOT: BOLT-INFO
CHECK-NOT: BOLT-WARNING
Expand All @@ -16,4 +16,4 @@ CHECK-NOT: BOLT-ERROR
CHECK-LOG: BOLT-INFO: Target architecture
CHECK-LOG: BOLT-INFO: BOLT version
CHECK-LOG: BOLT-INFO: basic block reordering modified layout
CHECK-LOG: Binary Function "usqrt"
CHECK-LOG: Binary Function "main"
29 changes: 29 additions & 0 deletions bolt/test/X86/print-only-section.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
## Check that --print-only flag works with sections.

# REQUIRES: system-linux

# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-linux %s -o %t.o
# RUN: ld.lld %t.o -o %t.exe
# RUN: llvm-bolt %t.exe -o %t.out --print-cfg --print-only=unused_code 2>&1 \
# RUN: | FileCheck %s

# CHECK: Binary Function "foo"
# CHECK-NOT: Binary Function "_start"

.text
.globl _start
.type _start, %function
_start:
.cfi_startproc
ret
.cfi_endproc
.size _start, .-_start

.section unused_code,"ax",@progbits
.globl foo
.type foo, %function
foo:
.cfi_startproc
ret
.cfi_endproc
.size foo, .-foo
4 changes: 2 additions & 2 deletions bolt/test/lit.local.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
host_linux_triple = config.target_triple.split("-")[0] + "-unknown-linux-gnu"
common_linker_flags = "-fuse-ld=lld -Wl,--unresolved-symbols=ignore-all"
flags = f"--target={host_linux_triple} {common_linker_flags}"
common_linker_flags = "-fuse-ld=lld -Wl,--unresolved-symbols=ignore-all -pie"
flags = f"--target={host_linux_triple} -fPIE {common_linker_flags}"

config.substitutions.insert(0, ("%cflags", f"%cflags {flags}"))
config.substitutions.insert(0, ("%cxxflags", f"%cxxflags {flags}"))
45 changes: 45 additions & 0 deletions bolt/test/merge-fdata-uninitialized-header.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
## Test that merge-fdata correctly handles YAML header with an uninitialized
## fields. a.yaml does not have hash-func set and it used to crash merge-fdata.

# REQUIRES: system-linux

# RUN: split-file %s %t
# RUN: not merge-fdata %t/a.yaml %t/b.yaml 2>&1 | FileCheck %s

# CHECK: cannot merge profiles with different hash functions

#--- a.yaml
---
header:
profile-version: 1
binary-name: 'a.out'
binary-build-id: '<unknown>'
profile-flags: [ lbr ]
profile-origin: branch profile reader
profile-events: ''
dfs-order: false
functions:
- name: 'main'
fid: 1
hash: 0x50BBA3441D436491
exec: 1
nblocks: 0
...
#--- b.yaml
---
header:
profile-version: 1
binary-name: 'a.out'
binary-build-id: '<unknown>'
profile-flags: [ lbr ]
profile-origin: branch profile reader
profile-events: ''
dfs-order: false
hash-func: xxh3
functions:
- name: 'main'
fid: 1
hash: 0x50BBA3441D436491
exec: 1
nblocks: 0
...
5 changes: 3 additions & 2 deletions bolt/test/perf2bolt/lit.local.cfg
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import shutil
import subprocess

if shutil.which("perf") is not None:
config.available_features.add("perf")
if shutil.which("perf") is not None and subprocess.run(["perf", "record", "-e", "cycles:u", "-o", "/dev/null", "--", "perf", "--version"], capture_output=True).returncode == 0:
config.available_features.add("perf")
9 changes: 3 additions & 6 deletions bolt/test/perf2bolt/perf_test.test
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,12 @@
REQUIRES: system-linux, perf

RUN: %clang %S/Inputs/perf_test.c -fuse-ld=lld -Wl,--script=%S/Inputs/perf_test.lds -o %t
RUN: perf record -e cycles:u -o %t2 -- %t
RUN: perf record -Fmax -e cycles:u -o %t2 -- %t
RUN: perf2bolt %t -p=%t2 -o %t3 -nl -ignore-build-id 2>&1 | FileCheck %s

CHECK-NOT: PERF2BOLT-ERROR
CHECK-NOT: !! WARNING !! This high mismatch ratio indicates the input binary is probably not the same binary used during profiling collection.

RUN: %clang %S/Inputs/perf_test.c -no-pie -fuse-ld=lld -o %t4
RUN: perf record -e cycles:u -o %t5 -- %t4
RUN: perf2bolt %t4 -p=%t5 -o %t6 -nl -ignore-build-id 2>&1 | FileCheck %s --check-prefix=CHECK-NO-PIE

CHECK-NO-PIE-NOT: PERF2BOLT-ERROR
CHECK-NO-PIE-NOT: !! WARNING !! This high mismatch ratio indicates the input binary is probably not the same binary used during profiling collection.
RUN: perf record -Fmax -e cycles:u -o %t5 -- %t4
RUN: perf2bolt %t4 -p=%t5 -o %t6 -nl -ignore-build-id 2>&1 | FileCheck %s
12 changes: 11 additions & 1 deletion bolt/tools/merge-fdata/merge-fdata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ void mergeProfileHeaders(BinaryProfileHeader &MergedHeader,
errs() << "WARNING: merging profiles with different sampling events\n";
MergedHeader.EventNames += "," + Header.EventNames;
}

if (MergedHeader.HashFunction != Header.HashFunction)
report_error("merge conflict",
"cannot merge profiles with different hash functions");
}

void mergeBasicBlockProfile(BinaryBasicBlockProfile &MergedBB,
Expand Down Expand Up @@ -386,6 +390,7 @@ int main(int argc, char **argv) {
// Merged information for all functions.
StringMap<BinaryFunctionProfile> MergedBFs;

bool FirstHeader = true;
for (std::string &InputDataFilename : Inputs) {
ErrorOr<std::unique_ptr<MemoryBuffer>> MB =
MemoryBuffer::getFileOrSTDIN(InputDataFilename);
Expand All @@ -409,7 +414,12 @@ int main(int argc, char **argv) {
}

// Merge the header.
mergeProfileHeaders(MergedHeader, BP.Header);
if (FirstHeader) {
MergedHeader = BP.Header;
FirstHeader = false;
} else {
mergeProfileHeaders(MergedHeader, BP.Header);
}

// Do the function merge.
for (BinaryFunctionProfile &BF : BP.Functions) {
Expand Down
36 changes: 28 additions & 8 deletions bolt/unittests/Core/BinaryContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,13 +160,14 @@ TEST_P(BinaryContextTester, FlushPendingRelocJUMP26) {
TEST_P(BinaryContextTester, BaseAddress) {
// Check that base address calculation is correct for a binary with the
// following segment layout:
BC->SegmentMapInfo[0] = SegmentInfo{0, 0x10e8c2b4, 0, 0x10e8c2b4, 0x1000};
BC->SegmentMapInfo[0] =
SegmentInfo{0, 0x10e8c2b4, 0, 0x10e8c2b4, 0x1000, true};
BC->SegmentMapInfo[0x10e8d2b4] =
SegmentInfo{0x10e8d2b4, 0x3952faec, 0x10e8c2b4, 0x3952faec, 0x1000};
SegmentInfo{0x10e8d2b4, 0x3952faec, 0x10e8c2b4, 0x3952faec, 0x1000, true};
BC->SegmentMapInfo[0x4a3bddc0] =
SegmentInfo{0x4a3bddc0, 0x148e828, 0x4a3bbdc0, 0x148e828, 0x1000};
SegmentInfo{0x4a3bddc0, 0x148e828, 0x4a3bbdc0, 0x148e828, 0x1000, true};
BC->SegmentMapInfo[0x4b84d5e8] =
SegmentInfo{0x4b84d5e8, 0x294f830, 0x4b84a5e8, 0x3d3820, 0x1000};
SegmentInfo{0x4b84d5e8, 0x294f830, 0x4b84a5e8, 0x3d3820, 0x1000, true};

std::optional<uint64_t> BaseAddress =
BC->getBaseAddressForMapping(0x7f13f5556000, 0x10e8c000);
Expand All @@ -181,13 +182,13 @@ TEST_P(BinaryContextTester, BaseAddress2) {
// Check that base address calculation is correct for a binary if the
// alignment in ELF file are different from pagesize.
// The segment layout is as follows:
BC->SegmentMapInfo[0] = SegmentInfo{0, 0x2177c, 0, 0x2177c, 0x10000};
BC->SegmentMapInfo[0] = SegmentInfo{0, 0x2177c, 0, 0x2177c, 0x10000, true};
BC->SegmentMapInfo[0x31860] =
SegmentInfo{0x31860, 0x370, 0x21860, 0x370, 0x10000};
SegmentInfo{0x31860, 0x370, 0x21860, 0x370, 0x10000, true};
BC->SegmentMapInfo[0x41c20] =
SegmentInfo{0x41c20, 0x1f8, 0x21c20, 0x1f8, 0x10000};
SegmentInfo{0x41c20, 0x1f8, 0x21c20, 0x1f8, 0x10000, true};
BC->SegmentMapInfo[0x54e18] =
SegmentInfo{0x54e18, 0x51, 0x24e18, 0x51, 0x10000};
SegmentInfo{0x54e18, 0x51, 0x24e18, 0x51, 0x10000, true};

std::optional<uint64_t> BaseAddress =
BC->getBaseAddressForMapping(0xaaaaea444000, 0x21000);
Expand All @@ -197,3 +198,22 @@ TEST_P(BinaryContextTester, BaseAddress2) {
BaseAddress = BC->getBaseAddressForMapping(0xaaaaea444000, 0x11000);
ASSERT_FALSE(BaseAddress.has_value());
}

TEST_P(BinaryContextTester, BaseAddressSegmentsSmallerThanAlignment) {
// Check that the correct segment is used to compute the base address
// when multiple segments are close together in the ELF file (closer
// than the required alignment in the process space).
// See https://github.com/llvm/llvm-project/issues/109384
BC->SegmentMapInfo[0] = SegmentInfo{0, 0x1d1c, 0, 0x1d1c, 0x10000, false};
BC->SegmentMapInfo[0x11d40] =
SegmentInfo{0x11d40, 0x11e0, 0x1d40, 0x11e0, 0x10000, true};
BC->SegmentMapInfo[0x22f20] =
SegmentInfo{0x22f20, 0x10e0, 0x2f20, 0x1f0, 0x10000, false};
BC->SegmentMapInfo[0x33110] =
SegmentInfo{0x33110, 0x89, 0x3110, 0x88, 0x10000, false};

std::optional<uint64_t> BaseAddress =
BC->getBaseAddressForMapping(0xaaaaaaab1000, 0x1000);
ASSERT_TRUE(BaseAddress.has_value());
ASSERT_EQ(*BaseAddress, 0xaaaaaaaa0000ULL);
}
1 change: 0 additions & 1 deletion clang-tools-extra/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ add_subdirectory(clang-move)
add_subdirectory(clang-query)
add_subdirectory(include-cleaner)
add_subdirectory(pp-trace)
add_subdirectory(pseudo)
add_subdirectory(tool-template)

option(CLANG_TOOLS_EXTRA_INCLUDE_DOCS "Generate build targets for the Clang Extra Tools docs."
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/CODE_OWNERS.TXT
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ D: clang-tidy

N: Manuel Klimek
E: klimek@google.com
D: clang-rename, all parts of clang-tools-extra not covered by someone else
D: all parts of clang-tools-extra not covered by someone else

N: Sam McCall
E: sammccall@google.com
Expand Down
7 changes: 3 additions & 4 deletions clang-tools-extra/clang-move/HelperDeclRefGraph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,10 @@ HelperDeclRefGraph::getReachableNodes(const Decl *Root) const {
llvm::DenseSet<const CallGraphNode *> ConnectedNodes;
std::function<void(const CallGraphNode *)> VisitNode =
[&](const CallGraphNode *Node) {
if (ConnectedNodes.count(Node))
if (!ConnectedNodes.insert(Node).second)
return;
ConnectedNodes.insert(Node);
for (auto It = Node->begin(), End = Node->end(); It != End; ++It)
VisitNode(*It);
for (const CallGraphNode::CallRecord &Callee : *Node)
VisitNode(Callee);
};

VisitNode(RootNode);
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-move/tool/ClangMove.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ int main(int argc, const char **argv) {
for (auto I = Files.begin(), E = Files.end(); I != E; ++I) {
OS << " {\n";
OS << " \"FilePath\": \"" << *I << "\",\n";
const auto Entry = FileMgr.getFile(*I);
const auto Entry = FileMgr.getOptionalFileRef(*I);
auto ID = SM.translateFile(*Entry);
std::string Content;
llvm::raw_string_ostream ContentStream(Content);
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clang-query/Query.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ bool MatchQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {
TD.emitDiagnostic(
FullSourceLoc(R.getBegin(), AST->getSourceManager()),
DiagnosticsEngine::Note, "\"" + BI->first + "\" binds here",
CharSourceRange::getTokenRange(R), std::nullopt);
CharSourceRange::getTokenRange(R), {});
}
}
if (QS.PrintOutput) {
Expand Down
2 changes: 0 additions & 2 deletions clang-tools-extra/clang-tidy/ClangTidyDiagnosticConsumer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,6 @@ void ClangTidyDiagnosticConsumer::HandleDiagnostic(
++Context.Stats.ErrorsIgnoredNOLINT;
// Ignored a warning, should ignore related notes as well
LastErrorWasIgnored = true;
Context.DiagEngine->Clear();
for (const auto &Error : SuppressionErrors)
Context.diag(Error);
return;
Expand Down Expand Up @@ -457,7 +456,6 @@ void ClangTidyDiagnosticConsumer::HandleDiagnostic(
if (Info.hasSourceManager())
checkFilters(Info.getLocation(), Info.getSourceManager());

Context.DiagEngine->Clear();
for (const auto &Error : SuppressionErrors)
Context.diag(Error);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,8 @@ void ExpandModularHeadersPPCallbacks::handleModuleFile(
if (!MF)
return;
// Avoid processing a ModuleFile more than once.
if (VisitedModules.count(MF))
if (!VisitedModules.insert(MF).second)
return;
VisitedModules.insert(MF);

// Visit all the input files of this module and mark them to record their
// contents later.
Expand Down
3 changes: 3 additions & 0 deletions clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
#include "SuspiciousStringviewDataUsageCheck.h"
#include "SwappedArgumentsCheck.h"
#include "SwitchMissingDefaultCaseCheck.h"
#include "TaggedUnionMemberCountCheck.h"
#include "TerminatingContinueCheck.h"
#include "ThrowKeywordMissingCheck.h"
#include "TooSmallLoopVariableCheck.h"
Expand Down Expand Up @@ -229,6 +230,8 @@ class BugproneModule : public ClangTidyModule {
"bugprone-suspicious-stringview-data-usage");
CheckFactories.registerCheck<SwappedArgumentsCheck>(
"bugprone-swapped-arguments");
CheckFactories.registerCheck<TaggedUnionMemberCountCheck>(
"bugprone-tagged-union-member-count");
CheckFactories.registerCheck<TerminatingContinueCheck>(
"bugprone-terminating-continue");
CheckFactories.registerCheck<ThrowKeywordMissingCheck>(
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ add_clang_library(clangTidyBugproneModule
SuspiciousSemicolonCheck.cpp
SuspiciousStringCompareCheck.cpp
SwappedArgumentsCheck.cpp
TaggedUnionMemberCountCheck.cpp
TerminatingContinueCheck.cpp
ThrowKeywordMissingCheck.cpp
TooSmallLoopVariableCheck.cpp
Expand Down
4 changes: 2 additions & 2 deletions clang-tools-extra/clang-tidy/bugprone/DanglingHandleCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ DanglingHandleCheck::DanglingHandleCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
HandleClasses(utils::options::parseStringList(Options.get(
"HandleClasses",
"std::basic_string_view;std::experimental::basic_string_view"))),
"HandleClasses", "std::basic_string_view;std::experimental::basic_"
"string_view;std::span"))),
IsAHandle(cxxRecordDecl(hasAnyName(HandleClasses)).bind("handle")) {}

void DanglingHandleCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ static std::string getNameOfNamespace(const CXXRecordDecl *Decl) {
std::string Ns;
llvm::raw_string_ostream OStream(Ns);
NsDecl->printQualifiedName(OStream);
OStream.flush();
return Ns.empty() ? "(global)" : Ns;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
#include "ForwardingReferenceOverloadCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include <algorithm>

using namespace clang::ast_matchers;

Expand All @@ -19,14 +18,14 @@ namespace {
// Check if the given type is related to std::enable_if.
AST_MATCHER(QualType, isEnableIf) {
auto CheckTemplate = [](const TemplateSpecializationType *Spec) {
if (!Spec || !Spec->getTemplateName().getAsTemplateDecl()) {
if (!Spec)
return false;
}
const NamedDecl *TypeDecl =
Spec->getTemplateName().getAsTemplateDecl()->getTemplatedDecl();
return TypeDecl->isInStdNamespace() &&
(TypeDecl->getName() == "enable_if" ||
TypeDecl->getName() == "enable_if_t");

const TemplateDecl *TDecl = Spec->getTemplateName().getAsTemplateDecl();

return TDecl && TDecl->isInStdNamespace() &&
(TDecl->getName() == "enable_if" ||
TDecl->getName() == "enable_if_t");
};
const Type *BaseType = Node.getTypePtr();
// Case: pointer or reference to enable_if.
Expand Down
61 changes: 35 additions & 26 deletions clang-tools-extra/clang-tidy/bugprone/PosixReturnCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,52 +7,58 @@
//===----------------------------------------------------------------------===//

#include "PosixReturnCheck.h"
#include "../utils/Matchers.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Lex/Lexer.h"

using namespace clang::ast_matchers;

namespace clang::tidy::bugprone {

static StringRef getFunctionSpelling(const MatchFinder::MatchResult &Result,
const char *BindingStr) {
const CallExpr *MatchedCall = cast<CallExpr>(
(Result.Nodes.getNodeAs<BinaryOperator>(BindingStr))->getLHS());
static StringRef getFunctionSpelling(const MatchFinder::MatchResult &Result) {
const auto *MatchedCall = Result.Nodes.getNodeAs<CallExpr>("call");
const SourceManager &SM = *Result.SourceManager;
return Lexer::getSourceText(CharSourceRange::getTokenRange(
MatchedCall->getCallee()->getSourceRange()),
SM, Result.Context->getLangOpts());
}

void PosixReturnCheck::registerMatchers(MatchFinder *Finder) {
const auto PosixCall =
callExpr(callee(functionDecl(
anyOf(matchesName("^::posix_"), matchesName("^::pthread_")),
unless(hasName("::posix_openpt")))))
.bind("call");
const auto ZeroIntegerLiteral = integerLiteral(equals(0));
const auto NegIntegerLiteral =
unaryOperator(hasOperatorName("-"), hasUnaryOperand(integerLiteral()));

Finder->addMatcher(
binaryOperator(
hasOperatorName("<"),
hasLHS(callExpr(callee(functionDecl(
anyOf(matchesName("^::posix_"), matchesName("^::pthread_")),
unless(hasName("::posix_openpt")))))),
hasRHS(integerLiteral(equals(0))))
anyOf(allOf(hasOperatorName("<"), hasLHS(PosixCall),
hasRHS(ZeroIntegerLiteral)),
allOf(hasOperatorName(">"), hasLHS(ZeroIntegerLiteral),
hasRHS(PosixCall))))
.bind("ltzop"),
this);
Finder->addMatcher(
binaryOperator(
hasOperatorName(">="),
hasLHS(callExpr(callee(functionDecl(
anyOf(matchesName("^::posix_"), matchesName("^::pthread_")),
unless(hasName("::posix_openpt")))))),
hasRHS(integerLiteral(equals(0))))
anyOf(allOf(hasOperatorName(">="), hasLHS(PosixCall),
hasRHS(ZeroIntegerLiteral)),
allOf(hasOperatorName("<="), hasLHS(ZeroIntegerLiteral),
hasRHS(PosixCall))))
.bind("atop"),
this);
Finder->addMatcher(binaryOperator(hasAnyOperatorName("==", "!="),
hasOperands(PosixCall, NegIntegerLiteral))
.bind("binop"),
this);
Finder->addMatcher(
binaryOperator(
hasAnyOperatorName("==", "!=", "<=", "<"),
hasLHS(callExpr(callee(functionDecl(
anyOf(matchesName("^::posix_"), matchesName("^::pthread_")),
unless(hasName("::posix_openpt")))))),
hasRHS(unaryOperator(hasOperatorName("-"),
hasUnaryOperand(integerLiteral()))))
binaryOperator(anyOf(allOf(hasAnyOperatorName("<=", "<"),
hasLHS(PosixCall), hasRHS(NegIntegerLiteral)),
allOf(hasAnyOperatorName(">", ">="),
hasLHS(NegIntegerLiteral), hasRHS(PosixCall))))
.bind("binop"),
this);
}
Expand All @@ -61,23 +67,26 @@ void PosixReturnCheck::check(const MatchFinder::MatchResult &Result) {
if (const auto *LessThanZeroOp =
Result.Nodes.getNodeAs<BinaryOperator>("ltzop")) {
SourceLocation OperatorLoc = LessThanZeroOp->getOperatorLoc();
StringRef NewBinOp =
LessThanZeroOp->getOpcode() == BinaryOperator::Opcode::BO_LT ? ">"
: "<";
diag(OperatorLoc, "the comparison always evaluates to false because %0 "
"always returns non-negative values")
<< getFunctionSpelling(Result, "ltzop")
<< FixItHint::CreateReplacement(OperatorLoc, Twine(">").str());
<< getFunctionSpelling(Result)
<< FixItHint::CreateReplacement(OperatorLoc, NewBinOp);
return;
}
if (const auto *AlwaysTrueOp =
Result.Nodes.getNodeAs<BinaryOperator>("atop")) {
diag(AlwaysTrueOp->getOperatorLoc(),
"the comparison always evaluates to true because %0 always returns "
"non-negative values")
<< getFunctionSpelling(Result, "atop");
<< getFunctionSpelling(Result);
return;
}
const auto *BinOp = Result.Nodes.getNodeAs<BinaryOperator>("binop");
diag(BinOp->getOperatorLoc(), "%0 only returns non-negative values")
<< getFunctionSpelling(Result, "binop");
<< getFunctionSpelling(Result);
}

} // namespace clang::tidy::bugprone
103 changes: 91 additions & 12 deletions clang-tools-extra/clang-tidy/bugprone/SizeofExpressionCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ AST_MATCHER_P2(Expr, hasSizeOfDescendant, int, Depth,
return false;
}

AST_MATCHER(Expr, offsetOfExpr) { return isa<OffsetOfExpr>(Node); }

CharUnits getSizeOfType(const ASTContext &Ctx, const Type *Ty) {
if (!Ty || Ty->isIncompleteType() || Ty->isDependentType() ||
isa<DependentSizedArrayType>(Ty) || !Ty->isConstantSizeType())
Expand Down Expand Up @@ -221,17 +223,15 @@ void SizeofExpressionCheck::registerMatchers(MatchFinder *Finder) {
const auto ElemType =
arrayType(hasElementType(recordType().bind("elem-type")));
const auto ElemPtrType = pointerType(pointee(type().bind("elem-ptr-type")));
const auto SizeofDivideExpr = binaryOperator(
hasOperatorName("/"),
hasLHS(
ignoringParenImpCasts(sizeOfExpr(hasArgumentOfType(hasCanonicalType(
type(anyOf(ElemType, ElemPtrType, type())).bind("num-type")))))),
hasRHS(ignoringParenImpCasts(sizeOfExpr(
hasArgumentOfType(hasCanonicalType(type().bind("denom-type")))))));

Finder->addMatcher(
binaryOperator(
hasOperatorName("/"),
hasLHS(ignoringParenImpCasts(sizeOfExpr(hasArgumentOfType(
hasCanonicalType(type(anyOf(ElemType, ElemPtrType, type()))
.bind("num-type")))))),
hasRHS(ignoringParenImpCasts(sizeOfExpr(
hasArgumentOfType(hasCanonicalType(type().bind("denom-type")))))))
.bind("sizeof-divide-expr"),
this);
Finder->addMatcher(SizeofDivideExpr.bind("sizeof-divide-expr"), this);

// Detect expression like: sizeof(...) * sizeof(...)); most likely an error.
Finder->addMatcher(binaryOperator(hasOperatorName("*"),
Expand All @@ -257,8 +257,9 @@ void SizeofExpressionCheck::registerMatchers(MatchFinder *Finder) {
.bind("sizeof-sizeof-expr"),
this);

// Detect sizeof in pointer arithmetic like: N * sizeof(S) == P1 - P2 or
// (P1 - P2) / sizeof(S) where P1 and P2 are pointers to type S.
// Detect sizeof usage in comparisons involving pointer arithmetics, such as
// N * sizeof(T) == P1 - P2 or (P1 - P2) / sizeof(T), where P1 and P2 are
// pointers to a type T.
const auto PtrDiffExpr = binaryOperator(
hasOperatorName("-"),
hasLHS(hasType(hasUnqualifiedDesugaredType(pointerType(pointee(
Expand All @@ -285,6 +286,47 @@ void SizeofExpressionCheck::registerMatchers(MatchFinder *Finder) {
hasRHS(ignoringParenImpCasts(SizeOfExpr.bind("sizeof-ptr-div-expr"))))
.bind("sizeof-in-ptr-arithmetic-div"),
this);

// SEI CERT ARR39-C. Do not add or subtract a scaled integer to a pointer.
// Detect sizeof, alignof and offsetof usage in pointer arithmetics where
// they are used to scale the numeric distance, which is scaled again by
// the pointer arithmetic operator. This can result in forming invalid
// offsets.
//
// Examples, where P is a pointer, N is some integer (both compile-time and
// run-time): P + sizeof(T), P + sizeof(*P), P + N * sizeof(*P).
//
// This check does not warn on cases where the pointee type is "1 byte",
// as those cases can often come from generics and also do not constitute a
// problem because the size does not affect the scale used.
const auto InterestingPtrTyForPtrArithmetic =
pointerType(pointee(qualType().bind("pointee-type")));
const auto SizeofLikeScaleExpr =
expr(anyOf(unaryExprOrTypeTraitExpr(ofKind(UETT_SizeOf)),
unaryExprOrTypeTraitExpr(ofKind(UETT_AlignOf)),
offsetOfExpr()))
.bind("sizeof-in-ptr-arithmetic-scale-expr");
const auto PtrArithmeticIntegerScaleExpr = binaryOperator(
hasAnyOperatorName("*", "/"),
// sizeof(...) * sizeof(...) and sizeof(...) / sizeof(...) is handled
// by this check on another path.
hasOperands(expr(hasType(isInteger()), unless(SizeofLikeScaleExpr)),
SizeofLikeScaleExpr));
const auto PtrArithmeticScaledIntegerExpr =
expr(anyOf(SizeofLikeScaleExpr, PtrArithmeticIntegerScaleExpr),
unless(SizeofDivideExpr));

Finder->addMatcher(
expr(anyOf(
binaryOperator(hasAnyOperatorName("+", "-"),
hasOperands(hasType(InterestingPtrTyForPtrArithmetic),
PtrArithmeticScaledIntegerExpr))
.bind("sizeof-in-ptr-arithmetic-plusminus"),
binaryOperator(hasAnyOperatorName("+=", "-="),
hasLHS(hasType(InterestingPtrTyForPtrArithmetic)),
hasRHS(PtrArithmeticScaledIntegerExpr))
.bind("sizeof-in-ptr-arithmetic-plusminus"))),
this);
}

void SizeofExpressionCheck::check(const MatchFinder::MatchResult &Result) {
Expand Down Expand Up @@ -409,6 +451,43 @@ void SizeofExpressionCheck::check(const MatchFinder::MatchResult &Result) {
<< SizeOfExpr->getSourceRange() << E->getOperatorLoc()
<< E->getLHS()->getSourceRange() << E->getRHS()->getSourceRange();
}
} else if (const auto *E = Result.Nodes.getNodeAs<BinaryOperator>(
"sizeof-in-ptr-arithmetic-plusminus")) {
const auto *PointeeTy = Result.Nodes.getNodeAs<QualType>("pointee-type");
const auto *ScaleExpr =
Result.Nodes.getNodeAs<Expr>("sizeof-in-ptr-arithmetic-scale-expr");
const CharUnits PointeeSize = getSizeOfType(Ctx, PointeeTy->getTypePtr());
const int ScaleKind = [ScaleExpr]() {
if (const auto *UTTE = dyn_cast<UnaryExprOrTypeTraitExpr>(ScaleExpr))
switch (UTTE->getKind()) {
case UETT_SizeOf:
return 0;
case UETT_AlignOf:
return 1;
default:
return -1;
}

if (isa<OffsetOfExpr>(ScaleExpr))
return 2;

return -1;
}();

if (ScaleKind != -1 && PointeeSize > CharUnits::One()) {
diag(E->getExprLoc(),
"suspicious usage of '%select{sizeof|alignof|offsetof}0(...)' in "
"pointer arithmetic; this scaled value will be scaled again by the "
"'%1' operator")
<< ScaleKind << E->getOpcodeStr() << ScaleExpr->getSourceRange();
diag(E->getExprLoc(),
"'%0' in pointer arithmetic internally scales with 'sizeof(%1)' == "
"%2",
DiagnosticIDs::Note)
<< E->getOpcodeStr()
<< PointeeTy->getAsString(Ctx.getPrintingPolicy())
<< PointeeSize.getQuantity();
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

namespace clang::tidy::bugprone {

/// Find suspicious usages of sizeof expression.
/// Find suspicious usages of sizeof expressions.
///
/// For the user-facing documentation see:
/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/sizeof-expression.html
Expand Down
199 changes: 199 additions & 0 deletions clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
//===--- TaggedUnionMemberCountCheck.cpp - clang-tidy ---------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "TaggedUnionMemberCountCheck.h"
#include "../utils/OptionsUtils.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallSet.h"

using namespace clang::ast_matchers;

namespace clang::tidy::bugprone {

static constexpr llvm::StringLiteral StrictModeOptionName = "StrictMode";
static constexpr llvm::StringLiteral EnableCountingEnumHeuristicOptionName =
"EnableCountingEnumHeuristic";
static constexpr llvm::StringLiteral CountingEnumPrefixesOptionName =
"CountingEnumPrefixes";
static constexpr llvm::StringLiteral CountingEnumSuffixesOptionName =
"CountingEnumSuffixes";

static constexpr bool StrictModeOptionDefaultValue = false;
static constexpr bool EnableCountingEnumHeuristicOptionDefaultValue = true;
static constexpr llvm::StringLiteral CountingEnumPrefixesOptionDefaultValue =
"";
static constexpr llvm::StringLiteral CountingEnumSuffixesOptionDefaultValue =
"count";

static constexpr llvm::StringLiteral RootMatchBindName = "root";
static constexpr llvm::StringLiteral UnionMatchBindName = "union";
static constexpr llvm::StringLiteral TagMatchBindName = "tags";

namespace {

AST_MATCHER_P2(RecordDecl, fieldCountOfKindIsOne,
ast_matchers::internal::Matcher<FieldDecl>, InnerMatcher,
StringRef, BindName) {
// BoundNodesTreeBuilder resets itself when a match occurs.
// So to avoid losing previously saved binds, a temporary instance
// is used for matching.
//
// For precedence, see commit: 5b07de1a5faf4a22ae6fd982b877c5e7e3a76559
clang::ast_matchers::internal::BoundNodesTreeBuilder TempBuilder;

const FieldDecl *FirstMatch = nullptr;
for (const FieldDecl *Field : Node.fields()) {
if (InnerMatcher.matches(*Field, Finder, &TempBuilder)) {
if (FirstMatch) {
return false;
} else {
FirstMatch = Field;
}
}
}

if (FirstMatch) {
Builder->setBinding(BindName, clang::DynTypedNode::create(*FirstMatch));
return true;
}
return false;
}

} // namespace

TaggedUnionMemberCountCheck::TaggedUnionMemberCountCheck(
StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
StrictMode(
Options.get(StrictModeOptionName, StrictModeOptionDefaultValue)),
EnableCountingEnumHeuristic(
Options.get(EnableCountingEnumHeuristicOptionName,
EnableCountingEnumHeuristicOptionDefaultValue)),
CountingEnumPrefixes(utils::options::parseStringList(
Options.get(CountingEnumPrefixesOptionName,
CountingEnumPrefixesOptionDefaultValue))),
CountingEnumSuffixes(utils::options::parseStringList(
Options.get(CountingEnumSuffixesOptionName,
CountingEnumSuffixesOptionDefaultValue))) {
if (!EnableCountingEnumHeuristic) {
if (Options.get(CountingEnumPrefixesOptionName))
configurationDiag("%0: Counting enum heuristic is disabled but "
"%1 is set")
<< Name << CountingEnumPrefixesOptionName;
if (Options.get(CountingEnumSuffixesOptionName))
configurationDiag("%0: Counting enum heuristic is disabled but "
"%1 is set")
<< Name << CountingEnumSuffixesOptionName;
}
}

void TaggedUnionMemberCountCheck::storeOptions(
ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, StrictModeOptionName, StrictMode);
Options.store(Opts, EnableCountingEnumHeuristicOptionName,
EnableCountingEnumHeuristic);
Options.store(Opts, CountingEnumPrefixesOptionName,
utils::options::serializeStringList(CountingEnumPrefixes));
Options.store(Opts, CountingEnumSuffixesOptionName,
utils::options::serializeStringList(CountingEnumSuffixes));
}

void TaggedUnionMemberCountCheck::registerMatchers(MatchFinder *Finder) {

auto UnionField = fieldDecl(hasType(qualType(
hasCanonicalType(recordType(hasDeclaration(recordDecl(isUnion())))))));

auto EnumField = fieldDecl(hasType(
qualType(hasCanonicalType(enumType(hasDeclaration(enumDecl()))))));

auto hasOneUnionField = fieldCountOfKindIsOne(UnionField, UnionMatchBindName);
auto hasOneEnumField = fieldCountOfKindIsOne(EnumField, TagMatchBindName);

Finder->addMatcher(recordDecl(anyOf(isStruct(), isClass()), hasOneUnionField,
hasOneEnumField, unless(isImplicit()))
.bind(RootMatchBindName),
this);
}

bool TaggedUnionMemberCountCheck::isCountingEnumLikeName(StringRef Name) const {
if (llvm::any_of(CountingEnumPrefixes, [Name](StringRef Prefix) -> bool {
return Name.starts_with_insensitive(Prefix);
}))
return true;
if (llvm::any_of(CountingEnumSuffixes, [Name](StringRef Suffix) -> bool {
return Name.ends_with_insensitive(Suffix);
}))
return true;
return false;
}

std::pair<const std::size_t, const EnumConstantDecl *>
TaggedUnionMemberCountCheck::getNumberOfEnumValues(const EnumDecl *ED) {
llvm::SmallSet<llvm::APSInt, 16> EnumValues;

const EnumConstantDecl *LastEnumConstant = nullptr;
for (const EnumConstantDecl *Enumerator : ED->enumerators()) {
EnumValues.insert(Enumerator->getInitVal());
LastEnumConstant = Enumerator;
}

if (EnableCountingEnumHeuristic && LastEnumConstant &&
isCountingEnumLikeName(LastEnumConstant->getName()) &&
(LastEnumConstant->getInitVal() == (EnumValues.size() - 1))) {
return {EnumValues.size() - 1, LastEnumConstant};
}

return {EnumValues.size(), nullptr};
}

void TaggedUnionMemberCountCheck::check(
const MatchFinder::MatchResult &Result) {
const auto *Root = Result.Nodes.getNodeAs<RecordDecl>(RootMatchBindName);
const auto *UnionField =
Result.Nodes.getNodeAs<FieldDecl>(UnionMatchBindName);
const auto *TagField = Result.Nodes.getNodeAs<FieldDecl>(TagMatchBindName);

assert(Root && "Root is missing!");
assert(UnionField && "UnionField is missing!");
assert(TagField && "TagField is missing!");
if (!Root || !UnionField || !TagField)
return;

const auto *UnionDef =
UnionField->getType().getCanonicalType().getTypePtr()->getAsRecordDecl();
const auto *EnumDef = llvm::dyn_cast<EnumDecl>(
TagField->getType().getCanonicalType().getTypePtr()->getAsTagDecl());

assert(UnionDef && "UnionDef is missing!");
assert(EnumDef && "EnumDef is missing!");
if (!UnionDef || !EnumDef)
return;

const std::size_t UnionMemberCount = llvm::range_size(UnionDef->fields());
auto [TagCount, CountingEnumConstantDecl] = getNumberOfEnumValues(EnumDef);

if (UnionMemberCount > TagCount) {
diag(Root->getLocation(),
"tagged union has more data members (%0) than tags (%1)!")
<< UnionMemberCount << TagCount;
} else if (StrictMode && UnionMemberCount < TagCount) {
diag(Root->getLocation(),
"tagged union has fewer data members (%0) than tags (%1)!")
<< UnionMemberCount << TagCount;
}

if (CountingEnumConstantDecl) {
diag(CountingEnumConstantDecl->getLocation(),
"assuming that this constant is just an auxiliary value and not "
"used for indicating a valid union data member",
DiagnosticIDs::Note);
}
}

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

#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_TAGGEDUNIONMEMBERCOUNTCHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_TAGGEDUNIONMEMBERCOUNTCHECK_H

#include "../ClangTidyCheck.h"

namespace clang::tidy::bugprone {

/// Gives warnings for tagged unions, where the number of tags is
/// different from the number of data members inside the union.
///
/// For the user-facing documentation see:
/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/tagged-union-member-count.html
class TaggedUnionMemberCountCheck : public ClangTidyCheck {
public:
TaggedUnionMemberCountCheck(StringRef Name, ClangTidyContext *Context);
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;

private:
const bool StrictMode;
const bool EnableCountingEnumHeuristic;
const std::vector<StringRef> CountingEnumPrefixes;
const std::vector<StringRef> CountingEnumSuffixes;

std::pair<const std::size_t, const EnumConstantDecl *>
getNumberOfEnumValues(const EnumDecl *ED);
bool isCountingEnumLikeName(StringRef Name) const;
};

} // namespace clang::tidy::bugprone

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_TAGGEDUNIONMEMBERCOUNTCHECK_H
178 changes: 143 additions & 35 deletions clang-tools-extra/clang-tidy/bugprone/UnsafeFunctionsCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//

#include "UnsafeFunctionsCheck.h"
#include "../utils/OptionsUtils.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Lex/PPCallbacks.h"
Expand All @@ -18,6 +19,10 @@ using namespace llvm;

namespace clang::tidy::bugprone {

static constexpr llvm::StringLiteral OptionNameCustomFunctions =
"CustomFunctions";
static constexpr llvm::StringLiteral OptionNameReportDefaultFunctions =
"ReportDefaultFunctions";
static constexpr llvm::StringLiteral OptionNameReportMoreUnsafeFunctions =
"ReportMoreUnsafeFunctions";

Expand All @@ -26,6 +31,8 @@ static constexpr llvm::StringLiteral FunctionNamesWithAnnexKReplacementId =
static constexpr llvm::StringLiteral FunctionNamesId = "FunctionsNames";
static constexpr llvm::StringLiteral AdditionalFunctionNamesId =
"AdditionalFunctionsNames";
static constexpr llvm::StringLiteral CustomFunctionNamesId =
"CustomFunctionNames";
static constexpr llvm::StringLiteral DeclRefId = "DRE";

static std::optional<std::string>
Expand Down Expand Up @@ -127,57 +134,128 @@ static bool isAnnexKAvailable(std::optional<bool> &CacheVar, Preprocessor *PP,
return CacheVar.value();
}

static std::vector<UnsafeFunctionsCheck::CheckedFunction>
parseCheckedFunctions(StringRef Option, ClangTidyContext *Context) {
const std::vector<StringRef> Functions =
utils::options::parseStringList(Option);
std::vector<UnsafeFunctionsCheck::CheckedFunction> Result;
Result.reserve(Functions.size());

for (StringRef Function : Functions) {
if (Function.empty())
continue;

const auto [Name, Rest] = Function.split(',');
const auto [Replacement, Reason] = Rest.split(',');

if (Name.trim().empty()) {
Context->configurationDiag("invalid configuration value for option '%0'; "
"expected the name of an unsafe function")
<< OptionNameCustomFunctions;
continue;
}

Result.push_back(
{Name.trim().str(),
matchers::MatchesAnyListedNameMatcher::NameMatcher(Name.trim()),
Replacement.trim().str(), Reason.trim().str()});
}

return Result;
}

static std::string serializeCheckedFunctions(
const std::vector<UnsafeFunctionsCheck::CheckedFunction> &Functions) {
std::vector<std::string> Result;
Result.reserve(Functions.size());

for (const auto &Entry : Functions) {
if (Entry.Reason.empty())
Result.push_back(Entry.Name + "," + Entry.Replacement);
else
Result.push_back(Entry.Name + "," + Entry.Replacement + "," +
Entry.Reason);
}

return llvm::join(Result, ";");
}

UnsafeFunctionsCheck::UnsafeFunctionsCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
CustomFunctions(parseCheckedFunctions(
Options.get(OptionNameCustomFunctions, ""), Context)),
ReportDefaultFunctions(
Options.get(OptionNameReportDefaultFunctions, true)),
ReportMoreUnsafeFunctions(
Options.get(OptionNameReportMoreUnsafeFunctions, true)) {}

void UnsafeFunctionsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, OptionNameCustomFunctions,
serializeCheckedFunctions(CustomFunctions));
Options.store(Opts, OptionNameReportDefaultFunctions, ReportDefaultFunctions);
Options.store(Opts, OptionNameReportMoreUnsafeFunctions,
ReportMoreUnsafeFunctions);
}

void UnsafeFunctionsCheck::registerMatchers(MatchFinder *Finder) {
if (getLangOpts().C11) {
// Matching functions with safe replacements only in Annex K.
auto FunctionNamesWithAnnexKReplacementMatcher = hasAnyName(
"::bsearch", "::ctime", "::fopen", "::fprintf", "::freopen", "::fscanf",
"::fwprintf", "::fwscanf", "::getenv", "::gmtime", "::localtime",
"::mbsrtowcs", "::mbstowcs", "::memcpy", "::memmove", "::memset",
"::printf", "::qsort", "::scanf", "::snprintf", "::sprintf", "::sscanf",
"::strcat", "::strcpy", "::strerror", "::strlen", "::strncat",
"::strncpy", "::strtok", "::swprintf", "::swscanf", "::vfprintf",
"::vfscanf", "::vfwprintf", "::vfwscanf", "::vprintf", "::vscanf",
"::vsnprintf", "::vsprintf", "::vsscanf", "::vswprintf", "::vswscanf",
"::vwprintf", "::vwscanf", "::wcrtomb", "::wcscat", "::wcscpy",
"::wcslen", "::wcsncat", "::wcsncpy", "::wcsrtombs", "::wcstok",
"::wcstombs", "::wctomb", "::wmemcpy", "::wmemmove", "::wprintf",
"::wscanf");
if (ReportDefaultFunctions) {
if (getLangOpts().C11) {
// Matching functions with safe replacements only in Annex K.
auto FunctionNamesWithAnnexKReplacementMatcher = hasAnyName(
"::bsearch", "::ctime", "::fopen", "::fprintf", "::freopen",
"::fscanf", "::fwprintf", "::fwscanf", "::getenv", "::gmtime",
"::localtime", "::mbsrtowcs", "::mbstowcs", "::memcpy", "::memmove",
"::memset", "::printf", "::qsort", "::scanf", "::snprintf",
"::sprintf", "::sscanf", "::strcat", "::strcpy", "::strerror",
"::strlen", "::strncat", "::strncpy", "::strtok", "::swprintf",
"::swscanf", "::vfprintf", "::vfscanf", "::vfwprintf", "::vfwscanf",
"::vprintf", "::vscanf", "::vsnprintf", "::vsprintf", "::vsscanf",
"::vswprintf", "::vswscanf", "::vwprintf", "::vwscanf", "::wcrtomb",
"::wcscat", "::wcscpy", "::wcslen", "::wcsncat", "::wcsncpy",
"::wcsrtombs", "::wcstok", "::wcstombs", "::wctomb", "::wmemcpy",
"::wmemmove", "::wprintf", "::wscanf");
Finder->addMatcher(
declRefExpr(to(functionDecl(FunctionNamesWithAnnexKReplacementMatcher)
.bind(FunctionNamesWithAnnexKReplacementId)))
.bind(DeclRefId),
this);
}

// Matching functions with replacements without Annex K.
auto FunctionNamesMatcher =
hasAnyName("::asctime", "asctime_r", "::gets", "::rewind", "::setbuf");
Finder->addMatcher(
declRefExpr(to(functionDecl(FunctionNamesWithAnnexKReplacementMatcher)
.bind(FunctionNamesWithAnnexKReplacementId)))
declRefExpr(
to(functionDecl(FunctionNamesMatcher).bind(FunctionNamesId)))
.bind(DeclRefId),
this);

if (ReportMoreUnsafeFunctions) {
// Matching functions with replacements without Annex K, at user request.
auto AdditionalFunctionNamesMatcher =
hasAnyName("::bcmp", "::bcopy", "::bzero", "::getpw", "::vfork");
Finder->addMatcher(
declRefExpr(to(functionDecl(AdditionalFunctionNamesMatcher)
.bind(AdditionalFunctionNamesId)))
.bind(DeclRefId),
this);
}
}

// Matching functions with replacements without Annex K.
auto FunctionNamesMatcher =
hasAnyName("::asctime", "asctime_r", "::gets", "::rewind", "::setbuf");
Finder->addMatcher(
declRefExpr(to(functionDecl(FunctionNamesMatcher).bind(FunctionNamesId)))
.bind(DeclRefId),
this);

if (ReportMoreUnsafeFunctions) {
// Matching functions with replacements without Annex K, at user request.
auto AdditionalFunctionNamesMatcher =
hasAnyName("::bcmp", "::bcopy", "::bzero", "::getpw", "::vfork");
Finder->addMatcher(
declRefExpr(to(functionDecl(AdditionalFunctionNamesMatcher)
.bind(AdditionalFunctionNamesId)))
.bind(DeclRefId),
this);
if (!CustomFunctions.empty()) {
std::vector<llvm::StringRef> FunctionNames;
FunctionNames.reserve(CustomFunctions.size());

for (const auto &Entry : CustomFunctions)
FunctionNames.push_back(Entry.Name);

auto CustomFunctionsMatcher = matchers::matchesAnyListedName(FunctionNames);

Finder->addMatcher(declRefExpr(to(functionDecl(CustomFunctionsMatcher)
.bind(CustomFunctionNamesId)))
.bind(DeclRefId),
this);
}
}

Expand All @@ -186,16 +264,46 @@ void UnsafeFunctionsCheck::check(const MatchFinder::MatchResult &Result) {
const auto *FuncDecl = cast<FunctionDecl>(DeclRef->getDecl());
assert(DeclRef && FuncDecl && "No valid matched node in check()");

// Only one of these are matched at a time.
const auto *AnnexK = Result.Nodes.getNodeAs<FunctionDecl>(
FunctionNamesWithAnnexKReplacementId);
const auto *Normal = Result.Nodes.getNodeAs<FunctionDecl>(FunctionNamesId);
const auto *Additional =
Result.Nodes.getNodeAs<FunctionDecl>(AdditionalFunctionNamesId);
assert((AnnexK || Normal || Additional) && "No valid match category.");
const auto *Custom =
Result.Nodes.getNodeAs<FunctionDecl>(CustomFunctionNamesId);
assert((AnnexK || Normal || Additional || Custom) &&
"No valid match category.");

bool AnnexKIsAvailable =
isAnnexKAvailable(IsAnnexKAvailable, PP, getLangOpts());
StringRef FunctionName = FuncDecl->getName();

if (Custom) {
for (const auto &Entry : CustomFunctions) {
if (Entry.Pattern.match(*FuncDecl)) {
StringRef Reason =
Entry.Reason.empty() ? "is marked as unsafe" : Entry.Reason.c_str();

if (Entry.Replacement.empty()) {
diag(DeclRef->getExprLoc(), "function %0 %1; it should not be used")
<< FuncDecl << Reason << Entry.Replacement
<< DeclRef->getSourceRange();
} else {
diag(DeclRef->getExprLoc(),
"function %0 %1; '%2' should be used instead")
<< FuncDecl << Reason << Entry.Replacement
<< DeclRef->getSourceRange();
}

return;
}
}

llvm_unreachable("No custom function was matched.");
return;
}

const std::optional<std::string> ReplacementFunctionName =
[&]() -> std::optional<std::string> {
if (AnnexK) {
Expand Down
12 changes: 12 additions & 0 deletions clang-tools-extra/clang-tidy/bugprone/UnsafeFunctionsCheck.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNSAFEFUNCTIONSCHECK_H

#include "../ClangTidyCheck.h"
#include "../utils/Matchers.h"
#include <optional>

namespace clang::tidy::bugprone {
Expand All @@ -32,7 +33,18 @@ class UnsafeFunctionsCheck : public ClangTidyCheck {
Preprocessor *ModuleExpanderPP) override;
void onEndOfTranslationUnit() override;

struct CheckedFunction {
std::string Name;
matchers::MatchesAnyListedNameMatcher::NameMatcher Pattern;
std::string Replacement;
std::string Reason;
};

private:
const std::vector<CheckedFunction> CustomFunctions;

// If true, the default set of functions are reported.
const bool ReportDefaultFunctions;
/// If true, additional functions from widely used API-s (such as POSIX) are
/// added to the list of reported functions.
const bool ReportMoreUnsafeFunctions;
Expand Down
10 changes: 10 additions & 0 deletions clang-tools-extra/clang-tidy/cert/CERTTidyModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "../bugprone/ReservedIdentifierCheck.h"
#include "../bugprone/SignalHandlerCheck.h"
#include "../bugprone/SignedCharMisuseCheck.h"
#include "../bugprone/SizeofExpressionCheck.h"
#include "../bugprone/SpuriouslyWakeUpFunctionsCheck.h"
#include "../bugprone/SuspiciousMemoryComparisonCheck.h"
#include "../bugprone/UnhandledSelfAssignmentCheck.h"
Expand Down Expand Up @@ -281,6 +282,9 @@ class CERTModule : public ClangTidyModule {
"cert-oop58-cpp");

// C checkers
// ARR
CheckFactories.registerCheck<bugprone::SizeofExpressionCheck>(
"cert-arr39-c");
// CON
CheckFactories.registerCheck<bugprone::SpuriouslyWakeUpFunctionsCheck>(
"cert-con36-c");
Expand Down Expand Up @@ -332,6 +336,12 @@ class CERTModule : public ClangTidyModule {
ClangTidyOptions getModuleOptions() override {
ClangTidyOptions Options;
ClangTidyOptions::OptionMap &Opts = Options.CheckOptions;
Opts["cert-arr39-c.WarnOnSizeOfConstant"] = "false";
Opts["cert-arr39-c.WarnOnSizeOfIntegerExpression"] = "false";
Opts["cert-arr39-c.WarnOnSizeOfThis"] = "false";
Opts["cert-arr39-c.WarnOnSizeOfCompareToConstant"] = "false";
Opts["cert-arr39-c.WarnOnSizeOfPointer"] = "false";
Opts["cert-arr39-c.WarnOnSizeOfPointerToAggregate"] = "false";
Opts["cert-dcl16-c.NewSuffixes"] = "L;LL;LU;LLU";
Opts["cert-err33-c.CheckedFunctions"] = CertErr33CCheckedFunctions;
Opts["cert-err33-c.AllowCastToVoid"] = "true";
Expand Down
22 changes: 19 additions & 3 deletions clang-tools-extra/clang-tidy/cert/FloatLoopCounter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,38 @@
#include "FloatLoopCounter.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"

using namespace clang::ast_matchers;

namespace clang::tidy::cert {

void FloatLoopCounter::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(
forStmt(hasIncrement(expr(hasType(realFloatingPointType())))).bind("for"),
forStmt(hasIncrement(forEachDescendant(
declRefExpr(hasType(realFloatingPointType()),
to(varDecl().bind("var")))
.bind("inc"))),
hasCondition(forEachDescendant(
declRefExpr(hasType(realFloatingPointType()),
to(varDecl(equalsBoundNode("var"))))
.bind("cond"))))
.bind("for"),
this);
}

void FloatLoopCounter::check(const MatchFinder::MatchResult &Result) {
const auto *FS = Result.Nodes.getNodeAs<ForStmt>("for");

diag(FS->getInc()->getExprLoc(), "loop induction expression should not have "
"floating-point type");
diag(FS->getInc()->getBeginLoc(), "loop induction expression should not have "
"floating-point type")
<< Result.Nodes.getNodeAs<DeclRefExpr>("inc")->getSourceRange()
<< Result.Nodes.getNodeAs<DeclRefExpr>("cond")->getSourceRange();

if (!FS->getInc()->getType()->isRealFloatingType())
if (const auto *V = Result.Nodes.getNodeAs<VarDecl>("var"))
diag(V->getBeginLoc(), "floating-point type loop induction variable",
DiagnosticIDs::Note);
}

} // namespace clang::tidy::cert
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ static void updateAssignmentLevel(
memberExpr(hasObjectExpression(cxxThisExpr()),
member(fieldDecl(indexNotLessThan(Field->getFieldIndex()))));
auto DeclMatcher = declRefExpr(
to(varDecl(unless(parmVarDecl()), hasDeclContext(equalsNode(Ctor)))));
to(valueDecl(unless(parmVarDecl()), hasDeclContext(equalsNode(Ctor)))));
const bool HasDependence = !match(expr(anyOf(MemberMatcher, DeclMatcher,
hasDescendant(MemberMatcher),
hasDescendant(DeclMatcher))),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ void DefinitionsInHeadersCheck::check(const MatchFinder::MatchResult &Result) {
// inline is not allowed for main function.
if (FD->isMain())
return;
diag(FD->getLocation(), /*Description=*/"make as 'inline'",
diag(FD->getLocation(), "mark the definition as 'inline'",
DiagnosticIDs::Note)
<< FixItHint::CreateInsertion(FD->getInnerLocStart(), "inline ");
} else if (const auto *VD = dyn_cast<VarDecl>(ND)) {
Expand Down
16 changes: 9 additions & 7 deletions clang-tools-extra/clang-tidy/misc/UnusedUsingDeclsCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ AST_MATCHER_P(DeducedTemplateSpecializationType, refsToTemplatedDecl,
return false;
}

AST_MATCHER_P(Type, asTagDecl, clang::ast_matchers::internal::Matcher<TagDecl>,
DeclMatcher) {
if (const TagDecl *ND = Node.getAsTagDecl())
return DeclMatcher.matches(*ND, Finder, Builder);
return false;
}

} // namespace

// A function that helps to tell whether a TargetDecl in a UsingDecl will be
Expand Down Expand Up @@ -61,7 +68,8 @@ void UnusedUsingDeclsCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(userDefinedLiteral().bind("used"), this);
Finder->addMatcher(
loc(elaboratedType(unless(hasQualifier(nestedNameSpecifier())),
hasUnqualifiedDesugaredType(type().bind("usedType")))),
hasUnqualifiedDesugaredType(
type(asTagDecl(tagDecl().bind("used")))))),
this);
// Cases where we can identify the UsingShadowDecl directly, rather than
// just its target.
Expand Down Expand Up @@ -139,12 +147,6 @@ void UnusedUsingDeclsCheck::check(const MatchFinder::MatchResult &Result) {
return;
}

if (const auto *T = Result.Nodes.getNodeAs<Type>("usedType")) {
if (const auto *ND = T->getAsTagDecl())
RemoveNamedDecl(ND);
return;
}

if (const auto *UsedShadow =
Result.Nodes.getNodeAs<UsingShadowDecl>("usedShadow")) {
removeFromFoundDecls(UsedShadow->getTargetDecl());
Expand Down
27 changes: 23 additions & 4 deletions clang-tools-extra/clang-tidy/modernize/AvoidCArraysCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "AvoidCArraysCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"

using namespace clang::ast_matchers;

Expand Down Expand Up @@ -60,6 +61,7 @@ void AvoidCArraysCheck::registerMatchers(MatchFinder *Finder) {

Finder->addMatcher(
typeLoc(hasValidBeginLoc(), hasType(arrayType()),
optionally(hasParent(parmVarDecl().bind("param_decl"))),
unless(anyOf(hasParent(parmVarDecl(isArgvOfMain())),
hasParent(varDecl(isExternC())),
hasParent(fieldDecl(
Expand All @@ -72,11 +74,28 @@ void AvoidCArraysCheck::registerMatchers(MatchFinder *Finder) {

void AvoidCArraysCheck::check(const MatchFinder::MatchResult &Result) {
const auto *ArrayType = Result.Nodes.getNodeAs<TypeLoc>("typeloc");

const bool IsInParam =
Result.Nodes.getNodeAs<ParmVarDecl>("param_decl") != nullptr;
const bool IsVLA = ArrayType->getTypePtr()->isVariableArrayType();
enum class RecommendType { Array, Vector, Span };
llvm::SmallVector<const char *> RecommendTypes{};
if (IsVLA) {
RecommendTypes.push_back("'std::vector'");
} else if (ArrayType->getTypePtr()->isIncompleteArrayType() && IsInParam) {
// in function parameter, we also don't know the size of
// IncompleteArrayType.
if (Result.Context->getLangOpts().CPlusPlus20)
RecommendTypes.push_back("'std::span'");
else {
RecommendTypes.push_back("'std::array'");
RecommendTypes.push_back("'std::vector'");
}
} else {
RecommendTypes.push_back("'std::array'");
}
diag(ArrayType->getBeginLoc(),
"do not declare %select{C-style|C VLA}0 arrays, use "
"%select{std::array<>|std::vector<>}0 instead")
<< ArrayType->getTypePtr()->isVariableArrayType();
"do not declare %select{C-style|C VLA}0 arrays, use %1 instead")
<< IsVLA << llvm::join(RecommendTypes, " or ");
}

} // namespace clang::tidy::modernize
6 changes: 3 additions & 3 deletions clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ StatementMatcher makeIteratorLoopMatcher(bool IsReverse) {
/// EndVarName: 'j' (as a VarDecl)
/// In the second example only:
/// EndCallName: 'container.size()' (as a CXXMemberCallExpr) or
/// 'size(contaner)' (as a CallExpr)
/// 'size(container)' (as a CallExpr)
///
/// Client code will need to make sure that:
/// - The containers on which 'size()' is called is the container indexed.
Expand Down Expand Up @@ -491,7 +491,7 @@ static bool isDirectMemberExpr(const Expr *E) {
}

/// Given an expression that represents an usage of an element from the
/// containter that we are iterating over, returns false when it can be
/// container that we are iterating over, returns false when it can be
/// guaranteed this element cannot be modified as a result of this usage.
static bool canBeModified(ASTContext *Context, const Expr *E) {
if (E->getType().isConstQualified())
Expand Down Expand Up @@ -922,7 +922,7 @@ bool LoopConvertCheck::isConvertible(ASTContext *Context,
const ast_matchers::BoundNodes &Nodes,
const ForStmt *Loop,
LoopFixerKind FixerKind) {
// In self contained diagnosics mode we don't want dependancies on other
// In self contained diagnostic mode we don't want dependencies on other
// loops, otherwise, If we already modified the range of this for loop, don't
// do any further updates on this iteration.
if (areDiagsSelfContained())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,11 @@ static FindArgsResult findArgs(const CallExpr *Call) {
return Result;
}

static SmallVector<FixItHint>
// Returns `true` as `first` only if a nested call to `std::min` or
// `std::max` was found. Checking if `FixItHint`s were generated is not enough,
// as the explicit casts that the check introduces may be generated without a
// nested `std::min` or `std::max` call.
static std::pair<bool, SmallVector<FixItHint>>
generateReplacements(const MatchFinder::MatchResult &Match,
const CallExpr *TopCall, const FindArgsResult &Result,
const bool IgnoreNonTrivialTypes,
Expand All @@ -91,13 +95,15 @@ generateReplacements(const MatchFinder::MatchResult &Match,
const bool IsResultTypeTrivial = ResultType.isTrivialType(*Match.Context);

if ((!IsResultTypeTrivial && IgnoreNonTrivialTypes))
return FixItHints;
return {false, FixItHints};

if (IsResultTypeTrivial &&
static_cast<std::uint64_t>(
Match.Context->getTypeSizeInChars(ResultType).getQuantity()) >
IgnoreTrivialTypesOfSizeAbove)
return FixItHints;
return {false, FixItHints};

bool FoundNestedCall = false;

for (const Expr *Arg : Result.Args) {
const auto *InnerCall = dyn_cast<CallExpr>(Arg->IgnoreParenImpCasts());
Expand Down Expand Up @@ -146,6 +152,9 @@ generateReplacements(const MatchFinder::MatchResult &Match,
*Match.Context))
continue;

// We have found a nested call
FoundNestedCall = true;

// remove the function call
FixItHints.push_back(
FixItHint::CreateRemoval(InnerCall->getCallee()->getSourceRange()));
Expand All @@ -168,7 +177,7 @@ generateReplacements(const MatchFinder::MatchResult &Match,
CharSourceRange::getTokenRange(InnerResult.First->getEndLoc())));
}

const SmallVector<FixItHint> InnerReplacements = generateReplacements(
const auto [_, InnerReplacements] = generateReplacements(
Match, InnerCall, InnerResult, IgnoreNonTrivialTypes,
IgnoreTrivialTypesOfSizeAbove);

Expand All @@ -189,7 +198,7 @@ generateReplacements(const MatchFinder::MatchResult &Match,
}
}

return FixItHints;
return {FoundNestedCall, FixItHints};
}

MinMaxUseInitializerListCheck::MinMaxUseInitializerListCheck(
Expand Down Expand Up @@ -238,11 +247,11 @@ void MinMaxUseInitializerListCheck::check(
const auto *TopCall = Match.Nodes.getNodeAs<CallExpr>("topCall");

const FindArgsResult Result = findArgs(TopCall);
const SmallVector<FixItHint> Replacements =
const auto [FoundNestedCall, Replacements] =
generateReplacements(Match, TopCall, Result, IgnoreNonTrivialTypes,
IgnoreTrivialTypesOfSizeAbove);

if (Replacements.empty())
if (!FoundNestedCall)
return;

const DiagnosticBuilder Diagnostic =
Expand Down
12 changes: 11 additions & 1 deletion clang-tools-extra/clang-tidy/modernize/UseNullptrCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,20 @@ AST_MATCHER(Type, sugaredNullptrType) {
/// to null within.
/// Finding sequences of explicit casts is necessary so that an entire sequence
/// can be replaced instead of just the inner-most implicit cast.
///
/// TODO/NOTE: The second "anyOf" below discards matches on a substituted type,
/// since we don't know if that would _always_ be a pointer type for all other
/// specializations, unless the expression was "__null", in which case we assume
/// that all specializations are expected to be for pointer types. Ideally this
/// would check for the "NULL" macro instead, but that'd be harder to express.
/// In practice, "NULL" is often defined as "__null", and this is a useful
/// condition.
StatementMatcher makeCastSequenceMatcher(llvm::ArrayRef<StringRef> NameList) {
auto ImplicitCastToNull = implicitCastExpr(
anyOf(hasCastKind(CK_NullToPointer), hasCastKind(CK_NullToMemberPointer)),
unless(hasImplicitDestinationType(qualType(substTemplateTypeParmType()))),
anyOf(hasSourceExpression(gnuNullExpr()),
unless(hasImplicitDestinationType(
qualType(substTemplateTypeParmType())))),
unless(hasSourceExpression(hasType(sugaredNullptrType()))),
unless(hasImplicitDestinationType(
qualType(matchers::matchesAnyListedTypeName(NameList)))));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ struct AvoidUnconditionalPreprocessorIfPPCallbacks : public PPCallbacks {
return (Tok.getRawIdentifier() == "true" ||
Tok.getRawIdentifier() == "false");
default:
return Tok.getKind() >= tok::l_square && Tok.getKind() <= tok::caretcaret;
return Tok.getKind() >= tok::l_square &&
Tok.getKind() <= tok::greatergreatergreater;
}
}

Expand Down
56 changes: 30 additions & 26 deletions clang-tools-extra/clang-tidy/readability/ContainerContainsCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,40 @@
using namespace clang::ast_matchers;

namespace clang::tidy::readability {

void ContainerContainsCheck::registerMatchers(MatchFinder *Finder) {
const auto SupportedContainers = hasType(
hasUnqualifiedDesugaredType(recordType(hasDeclaration(cxxRecordDecl(
hasAnyName("::std::set", "::std::unordered_set", "::std::map",
"::std::unordered_map", "::std::multiset",
"::std::unordered_multiset", "::std::multimap",
"::std::unordered_multimap"))))));
const auto HasContainsMatchingParamType = hasMethod(
cxxMethodDecl(isConst(), parameterCountIs(1), returns(booleanType()),
hasName("contains"), unless(isDeleted()), isPublic(),
hasParameter(0, hasType(hasUnqualifiedDesugaredType(
equalsBoundNode("parameterType"))))));

const auto CountCall =
cxxMemberCallExpr(on(SupportedContainers),
callee(cxxMethodDecl(hasName("count"))),
argumentCountIs(1))
cxxMemberCallExpr(
argumentCountIs(1),
callee(cxxMethodDecl(
hasName("count"),
hasParameter(0, hasType(hasUnqualifiedDesugaredType(
type().bind("parameterType")))),
ofClass(cxxRecordDecl(HasContainsMatchingParamType)))))
.bind("call");

const auto FindCall =
cxxMemberCallExpr(on(SupportedContainers),
callee(cxxMethodDecl(hasName("find"))),
argumentCountIs(1))
cxxMemberCallExpr(
argumentCountIs(1),
callee(cxxMethodDecl(
hasName("find"),
hasParameter(0, hasType(hasUnqualifiedDesugaredType(
type().bind("parameterType")))),
ofClass(cxxRecordDecl(HasContainsMatchingParamType)))))
.bind("call");

const auto EndCall = cxxMemberCallExpr(on(SupportedContainers),
callee(cxxMethodDecl(hasName("end"))),
argumentCountIs(0));
const auto EndCall = cxxMemberCallExpr(
argumentCountIs(0),
callee(
cxxMethodDecl(hasName("end"),
// In the matchers below, FindCall should always appear
// before EndCall so 'parameterType' is properly bound.
ofClass(cxxRecordDecl(HasContainsMatchingParamType)))));

const auto Literal0 = integerLiteral(equals(0));
const auto Literal1 = integerLiteral(equals(1));
Expand All @@ -52,10 +62,7 @@ void ContainerContainsCheck::registerMatchers(MatchFinder *Finder) {
.bind("positiveComparison"),
this);
AddSimpleMatcher(
binaryOperator(hasLHS(CountCall), hasOperatorName("!="), hasRHS(Literal0))
.bind("positiveComparison"));
AddSimpleMatcher(
binaryOperator(hasLHS(Literal0), hasOperatorName("!="), hasRHS(CountCall))
binaryOperator(hasOperatorName("!="), hasOperands(CountCall, Literal0))
.bind("positiveComparison"));
AddSimpleMatcher(
binaryOperator(hasLHS(CountCall), hasOperatorName(">"), hasRHS(Literal0))
Expand All @@ -72,10 +79,7 @@ void ContainerContainsCheck::registerMatchers(MatchFinder *Finder) {

// Find inverted membership tests which use `count()`.
AddSimpleMatcher(
binaryOperator(hasLHS(CountCall), hasOperatorName("=="), hasRHS(Literal0))
.bind("negativeComparison"));
AddSimpleMatcher(
binaryOperator(hasLHS(Literal0), hasOperatorName("=="), hasRHS(CountCall))
binaryOperator(hasOperatorName("=="), hasOperands(CountCall, Literal0))
.bind("negativeComparison"));
AddSimpleMatcher(
binaryOperator(hasLHS(CountCall), hasOperatorName("<="), hasRHS(Literal0))
Expand All @@ -92,10 +96,10 @@ void ContainerContainsCheck::registerMatchers(MatchFinder *Finder) {

// Find membership tests based on `find() == end()`.
AddSimpleMatcher(
binaryOperator(hasLHS(FindCall), hasOperatorName("!="), hasRHS(EndCall))
binaryOperator(hasOperatorName("!="), hasOperands(FindCall, EndCall))
.bind("positiveComparison"));
AddSimpleMatcher(
binaryOperator(hasLHS(FindCall), hasOperatorName("=="), hasRHS(EndCall))
binaryOperator(hasOperatorName("=="), hasOperands(FindCall, EndCall))
.bind("negativeComparison"));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@

namespace clang::tidy::readability {

/// Finds usages of `container.count()` and `find() == end()` which should be
/// replaced by a call to the `container.contains()` method introduced in C++20.
/// Finds usages of `container.count()` and
/// `container.find() == container.end()` which should be replaced by a call
/// to the `container.contains()` method.
///
/// For the user-facing documentation see:
/// http://clang.llvm.org/extra/clang-tidy/checks/readability/container-contains.html
Expand All @@ -24,10 +25,11 @@ class ContainerContainsCheck : public ClangTidyCheck {
: ClangTidyCheck(Name, Context) {}
void registerMatchers(ast_matchers::MatchFinder *Finder) final;
void check(const ast_matchers::MatchFinder::MatchResult &Result) final;

protected:
bool isLanguageVersionSupported(const LangOptions &LO) const final {
return LO.CPlusPlus20;
return LO.CPlusPlus;
}
std::optional<TraversalKind> getCheckTraversalKind() const override {
return TK_AsIs;
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,16 +141,18 @@ void EnumInitialValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
}

void EnumInitialValueCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(
enumDecl(unless(isMacro()), unless(hasConsistentInitialValues()))
.bind("inconsistent"),
this);
Finder->addMatcher(enumDecl(isDefinition(), unless(isMacro()),
unless(hasConsistentInitialValues()))
.bind("inconsistent"),
this);
if (!AllowExplicitZeroFirstInitialValue)
Finder->addMatcher(
enumDecl(hasZeroInitialValueForFirstEnumerator()).bind("zero_first"),
enumDecl(isDefinition(), hasZeroInitialValueForFirstEnumerator())
.bind("zero_first"),
this);
if (!AllowExplicitSequentialInitialValues)
Finder->addMatcher(enumDecl(unless(isMacro()), hasSequentialInitialValues())
Finder->addMatcher(enumDecl(isDefinition(), unless(isMacro()),
hasSequentialInitialValues())
.bind("sequential"),
this);
}
Expand All @@ -159,7 +161,7 @@ void EnumInitialValueCheck::check(const MatchFinder::MatchResult &Result) {
if (const auto *Enum = Result.Nodes.getNodeAs<EnumDecl>("inconsistent")) {
DiagnosticBuilder Diag =
diag(Enum->getBeginLoc(),
"inital values in enum %0 are not consistent, consider explicit "
"initial values in enum %0 are not consistent, consider explicit "
"initialization of all, none or only the first enumerator")
<< Enum;
for (const EnumConstantDecl *ECD : Enum->enumerators())
Expand Down
17 changes: 9 additions & 8 deletions clang-tools-extra/clang-tidy/utils/Matchers.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,7 @@ class MatchesAnyListedNameMatcher
NameList.begin(), NameList.end(), std::back_inserter(NameMatchers),
[](const llvm::StringRef Name) { return NameMatcher(Name); });
}
bool matches(
const NamedDecl &Node, ast_matchers::internal::ASTMatchFinder *Finder,
ast_matchers::internal::BoundNodesTreeBuilder *Builder) const override {
return llvm::any_of(NameMatchers, [&Node](const NameMatcher &NM) {
return NM.match(Node);
});
}

private:
class NameMatcher {
llvm::Regex Regex;
enum class MatchMode {
Expand Down Expand Up @@ -136,6 +128,15 @@ class MatchesAnyListedNameMatcher
}
};

bool matches(
const NamedDecl &Node, ast_matchers::internal::ASTMatchFinder *Finder,
ast_matchers::internal::BoundNodesTreeBuilder *Builder) const override {
return llvm::any_of(NameMatchers, [&Node](const NameMatcher &NM) {
return NM.match(Node);
});
}

private:
std::vector<NameMatcher> NameMatchers;
};

Expand Down
5 changes: 0 additions & 5 deletions clang-tools-extra/clangd/AST.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,6 @@ std::string printQualifiedName(const NamedDecl &ND) {
// In clangd, context is usually available and paths are mostly noise.
Policy.AnonymousTagLocations = false;
ND.printQualifiedName(OS, Policy);
OS.flush();
assert(!StringRef(QName).starts_with("::"));
return QName;
}
Expand Down Expand Up @@ -270,7 +269,6 @@ std::string printTemplateSpecializationArgs(const NamedDecl &ND) {
// location information.
printTemplateArgumentList(OS, Cls->getTemplateArgs().asArray(), Policy);
}
OS.flush();
return TemplateArgs;
}

Expand Down Expand Up @@ -303,7 +301,6 @@ std::string printObjCMethod(const ObjCMethodDecl &Method) {
OS << ", ...";

OS << ']';
OS.flush();
return Name;
}

Expand All @@ -314,15 +311,13 @@ std::string printObjCContainer(const ObjCContainerDecl &C) {
const ObjCInterfaceDecl *Class = Category->getClassInterface();
OS << getNameOrErrForObjCInterface(Class) << '(' << Category->getName()
<< ')';
OS.flush();
return Name;
}
if (const ObjCCategoryImplDecl *CID = dyn_cast<ObjCCategoryImplDecl>(&C)) {
std::string Name;
llvm::raw_string_ostream OS(Name);
const ObjCInterfaceDecl *Class = CID->getClassInterface();
OS << getNameOrErrForObjCInterface(Class) << '(' << CID->getName() << ')';
OS.flush();
return Name;
}
return C.getNameAsString();
Expand Down
1 change: 0 additions & 1 deletion clang-tools-extra/clangd/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,6 @@ target_link_libraries(clangDaemon
${LLVM_PTHREAD_LIB}

clangIncludeCleaner
clangPseudo
clangTidy
clangTidyUtils

Expand Down
5 changes: 4 additions & 1 deletion clang-tools-extra/clangd/CodeComplete.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1409,6 +1409,9 @@ bool semaCodeComplete(std::unique_ptr<CodeCompleteConsumer> Consumer,
Clang->getPreprocessorOpts().SingleFileParseMode = CompletingInPreamble;
Clang->setCodeCompletionConsumer(Consumer.release());

if (Input.Preamble.RequiredModules)
Input.Preamble.RequiredModules->adjustHeaderSearchOptions(Clang->getHeaderSearchOpts());

SyntaxOnlyAction Action;
if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])) {
log("BeginSourceFile() failed when running codeComplete for {0}",
Expand Down Expand Up @@ -2122,7 +2125,7 @@ clang::CodeCompleteOptions CodeCompleteOptions::getClangCompleteOpts() const {
// When an is used, Sema is responsible for completing the main file,
// the index can provide results from the preamble.
// Tell Sema not to deserialize the preamble to look for results.
Result.LoadExternal = !Index;
Result.LoadExternal = ForceLoadPreamble || !Index;
Result.IncludeFixIts = IncludeFixIts;

return Result;
Expand Down
5 changes: 5 additions & 0 deletions clang-tools-extra/clangd/CodeComplete.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ struct CodeCompleteOptions {
/// For example, private members are usually inaccessible.
bool IncludeIneligibleResults = false;

/// Force sema to load decls from preamble even if an index is provided.
/// This is helpful for cases the index can't provide symbols, e.g. with
/// experimental c++20 modules
bool ForceLoadPreamble = false;

/// Combine overloads into a single completion item where possible.
/// If none, the implementation may choose an appropriate behavior.
/// (In practice, ClangdLSPServer enables bundling if the client claims
Expand Down
29 changes: 7 additions & 22 deletions clang-tools-extra/clangd/Diagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,6 @@ std::string mainMessage(const Diag &D, const ClangdDiagnosticOptions &Opts) {
OS << "\n\n";
printDiag(OS, Note);
}
OS.flush();
return capitalize(std::move(Result));
}

Expand All @@ -335,7 +334,6 @@ std::string noteMessage(const Diag &Main, const DiagBase &Note,
OS << "\n\n";
printDiag(OS, Main);
}
OS.flush();
return capitalize(std::move(Result));
}

Expand Down Expand Up @@ -579,17 +577,7 @@ std::vector<Diag> StoreDiags::take(const clang::tidy::ClangTidyContext *Tidy) {
for (auto &Diag : Output) {
if (const char *ClangDiag = getDiagnosticCode(Diag.ID)) {
// Warnings controlled by -Wfoo are better recognized by that name.
const StringRef Warning = [&] {
if (OrigSrcMgr) {
return OrigSrcMgr->getDiagnostics()
.getDiagnosticIDs()
->getWarningOptionForDiag(Diag.ID);
}
if (!DiagnosticIDs::IsCustomDiag(Diag.ID))
return DiagnosticIDs{}.getWarningOptionForDiag(Diag.ID);
return StringRef{};
}();

StringRef Warning = DiagnosticIDs::getWarningOptionForDiag(Diag.ID);
if (!Warning.empty()) {
Diag.Name = ("-W" + Warning).str();
} else {
Expand Down Expand Up @@ -906,23 +894,20 @@ void StoreDiags::flushLastDiag() {
Output.push_back(std::move(*LastDiag));
}

bool isDiagnosticSuppressed(const clang::Diagnostic &Diag,
const llvm::StringSet<> &Suppress,
const LangOptions &LangOpts) {
bool isBuiltinDiagnosticSuppressed(unsigned ID,
const llvm::StringSet<> &Suppress,
const LangOptions &LangOpts) {
// Don't complain about header-only stuff in mainfiles if it's a header.
// FIXME: would be cleaner to suppress in clang, once we decide whether the
// behavior should be to silently-ignore or respect the pragma.
if (Diag.getID() == diag::pp_pragma_sysheader_in_main_file &&
LangOpts.IsHeaderFile)
if (ID == diag::pp_pragma_sysheader_in_main_file && LangOpts.IsHeaderFile)
return true;

if (const char *CodePtr = getDiagnosticCode(Diag.getID())) {
if (const char *CodePtr = getDiagnosticCode(ID)) {
if (Suppress.contains(normalizeSuppressedCode(CodePtr)))
return true;
}
StringRef Warning =
Diag.getDiags()->getDiagnosticIDs()->getWarningOptionForDiag(
Diag.getID());
StringRef Warning = DiagnosticIDs::getWarningOptionForDiag(ID);
if (!Warning.empty() && Suppress.contains(Warning))
return true;
return false;
Expand Down
8 changes: 4 additions & 4 deletions clang-tools-extra/clangd/Diagnostics.h
Original file line number Diff line number Diff line change
Expand Up @@ -181,11 +181,11 @@ class StoreDiags : public DiagnosticConsumer {
};

/// Determine whether a (non-clang-tidy) diagnostic is suppressed by config.
bool isDiagnosticSuppressed(const clang::Diagnostic &Diag,
const llvm::StringSet<> &Suppressed,
const LangOptions &);
bool isBuiltinDiagnosticSuppressed(unsigned ID,
const llvm::StringSet<> &Suppressed,
const LangOptions &);
/// Take a user-specified diagnostic code, and convert it to a normalized form
/// stored in the config and consumed by isDiagnosticsSuppressed.
/// stored in the config and consumed by isBuiltinDiagnosticsSuppressed.
///
/// (This strips err_ and -W prefix so we can match with or without them.)
llvm::StringRef normalizeSuppressedCode(llvm::StringRef);
Expand Down
1 change: 0 additions & 1 deletion clang-tools-extra/clangd/FindSymbols.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,6 @@ std::string getSymbolName(ASTContext &Ctx, const NamedDecl &ND) {
OS << (Method->isInstanceMethod() ? '-' : '+');
Method->getSelector().print(OS);

OS.flush();
return Name;
}
return printName(Ctx, ND);
Expand Down
4 changes: 0 additions & 4 deletions clang-tools-extra/clangd/Hover.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ std::string printDefinition(const Decl *D, PrintingPolicy PP,
std::string Definition;
llvm::raw_string_ostream OS(Definition);
D->print(OS, PP);
OS.flush();
return Definition;
}

Expand Down Expand Up @@ -179,7 +178,6 @@ HoverInfo::PrintedType printType(QualType QT, ASTContext &ASTCtx,
OS << TT->getDecl()->getKindName() << " ";
}
QT.print(OS, PP);
OS.flush();

const Config &Cfg = Config::current();
if (!QT.isNull() && Cfg.Hover.ShowAKA) {
Expand Down Expand Up @@ -229,7 +227,6 @@ HoverInfo::PrintedType printType(const TemplateTemplateParmDecl *TTP,
// FIXME: TemplateTemplateParameter doesn't store the info on whether this
// param was a "typename" or "class".
OS << "> class";
OS.flush();
return Result;
}

Expand Down Expand Up @@ -821,7 +818,6 @@ std::string typeAsDefinition(const HoverInfo::PrintedType &PType) {
OS << PType.Type;
if (PType.AKA)
OS << " // aka: " << *PType.AKA;
OS.flush();
return Result;
}

Expand Down
8 changes: 5 additions & 3 deletions clang-tools-extra/clangd/ParsedAST.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,8 @@ class TidyDiagnosticGroups {
llvm::StringRef Check;
while (!Checks.empty()) {
std::tie(Check, Checks) = Checks.split(',');
Check = Check.trim();

if (Check.empty())
continue;

Expand Down Expand Up @@ -340,7 +342,7 @@ void applyWarningOptions(llvm::ArrayRef<std::string> ExtraArgs,
if (Enable) {
if (Diags.getDiagnosticLevel(ID, SourceLocation()) <
DiagnosticsEngine::Warning) {
auto Group = Diags.getDiagnosticIDs()->getGroupForDiag(ID);
auto Group = DiagnosticIDs::getGroupForDiag(ID);
if (!Group || !EnabledGroups(*Group))
continue;
Diags.setSeverity(ID, diag::Severity::Warning, SourceLocation());
Expand Down Expand Up @@ -583,8 +585,8 @@ ParsedAST::build(llvm::StringRef Filename, const ParseInputs &Inputs,
ASTDiags.setLevelAdjuster([&](DiagnosticsEngine::Level DiagLevel,
const clang::Diagnostic &Info) {
if (Cfg.Diagnostics.SuppressAll ||
isDiagnosticSuppressed(Info, Cfg.Diagnostics.Suppress,
Clang->getLangOpts()))
isBuiltinDiagnosticSuppressed(Info.getID(), Cfg.Diagnostics.Suppress,
Clang->getLangOpts()))
return DiagnosticsEngine::Ignored;

auto It = OverriddenSeverity.find(Info.getID());
Expand Down
5 changes: 2 additions & 3 deletions clang-tools-extra/clangd/Preamble.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -621,8 +621,8 @@ buildPreamble(PathRef FileName, CompilerInvocation CI,
PreambleDiagnostics.setLevelAdjuster([&](DiagnosticsEngine::Level DiagLevel,
const clang::Diagnostic &Info) {
if (Cfg.Diagnostics.SuppressAll ||
isDiagnosticSuppressed(Info, Cfg.Diagnostics.Suppress,
CI.getLangOpts()))
isBuiltinDiagnosticSuppressed(Info.getID(), Cfg.Diagnostics.Suppress,
CI.getLangOpts()))
return DiagnosticsEngine::Ignored;
switch (Info.getID()) {
case diag::warn_no_newline_eof:
Expand Down Expand Up @@ -913,7 +913,6 @@ PreamblePatch PreamblePatch::create(llvm::StringRef FileName,
PP.PatchedMarks = std::move(ModifiedScan->Marks);
PP.PatchedMacros = std::move(ModifiedScan->Macros);
dlog("Created preamble patch: {0}", Patch.str());
Patch.flush();
return PP;
}

Expand Down
1 change: 0 additions & 1 deletion clang-tools-extra/clangd/Quality.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,6 @@ std::string sortText(float Score, llvm::StringRef Name) {
llvm::write_hex(OS, encodeFloat(-Score), llvm::HexPrintStyle::Lower,
/*Width=*/2 * sizeof(Score));
OS << Name;
OS.flush();
return S;
}

Expand Down
32 changes: 16 additions & 16 deletions clang-tools-extra/clangd/SemanticSelection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@
#include "Protocol.h"
#include "Selection.h"
#include "SourceCode.h"
#include "clang-pseudo/Bracket.h"
#include "clang-pseudo/DirectiveTree.h"
#include "clang-pseudo/Token.h"
#include "clang/AST/DeclBase.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
Expand All @@ -25,6 +22,9 @@
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Error.h"
#include "support/Bracket.h"
#include "support/DirectiveTree.h"
#include "support/Token.h"
#include <optional>
#include <queue>
#include <vector>
Expand Down Expand Up @@ -181,16 +181,16 @@ llvm::Expected<std::vector<FoldingRange>> getFoldingRanges(ParsedAST &AST) {
// Related issue: https://github.com/clangd/clangd/issues/310
llvm::Expected<std::vector<FoldingRange>>
getFoldingRanges(const std::string &Code, bool LineFoldingOnly) {
auto OrigStream = pseudo::lex(Code, clang::pseudo::genericLangOpts());
auto OrigStream = lex(Code, genericLangOpts());

auto DirectiveStructure = pseudo::DirectiveTree::parse(OrigStream);
pseudo::chooseConditionalBranches(DirectiveStructure, OrigStream);
auto DirectiveStructure = DirectiveTree::parse(OrigStream);
chooseConditionalBranches(DirectiveStructure, OrigStream);

// FIXME: Provide ranges in the disabled-PP regions as well.
auto Preprocessed = DirectiveStructure.stripDirectives(OrigStream);

auto ParseableStream = cook(Preprocessed, clang::pseudo::genericLangOpts());
pseudo::pairBrackets(ParseableStream);
auto ParseableStream = cook(Preprocessed, genericLangOpts());
pairBrackets(ParseableStream);

std::vector<FoldingRange> Result;
auto AddFoldingRange = [&](Position Start, Position End,
Expand All @@ -205,19 +205,19 @@ getFoldingRanges(const std::string &Code, bool LineFoldingOnly) {
FR.kind = Kind.str();
Result.push_back(FR);
};
auto OriginalToken = [&](const pseudo::Token &T) {
auto OriginalToken = [&](const Token &T) {
return OrigStream.tokens()[T.OriginalIndex];
};
auto StartOffset = [&](const pseudo::Token &T) {
auto StartOffset = [&](const Token &T) {
return OriginalToken(T).text().data() - Code.data();
};
auto StartPosition = [&](const pseudo::Token &T) {
auto StartPosition = [&](const Token &T) {
return offsetToPosition(Code, StartOffset(T));
};
auto EndOffset = [&](const pseudo::Token &T) {
auto EndOffset = [&](const Token &T) {
return StartOffset(T) + OriginalToken(T).Length;
};
auto EndPosition = [&](const pseudo::Token &T) {
auto EndPosition = [&](const Token &T) {
return offsetToPosition(Code, EndOffset(T));
};
auto Tokens = ParseableStream.tokens();
Expand All @@ -235,7 +235,7 @@ getFoldingRanges(const std::string &Code, bool LineFoldingOnly) {
}
}
}
auto IsBlockComment = [&](const pseudo::Token &T) {
auto IsBlockComment = [&](const Token &T) {
assert(T.Kind == tok::comment);
return OriginalToken(T).Length >= 2 &&
Code.substr(StartOffset(T), 2) == "/*";
Expand All @@ -246,10 +246,10 @@ getFoldingRanges(const std::string &Code, bool LineFoldingOnly) {
T++;
continue;
}
pseudo::Token *FirstComment = T;
Token *FirstComment = T;
// Show starting sentinals (// and /*) of the comment.
Position Start = offsetToPosition(Code, 2 + StartOffset(*FirstComment));
pseudo::Token *LastComment = T;
Token *LastComment = T;
Position End = EndPosition(*T);
while (T != Tokens.end() && T->Kind == tok::comment &&
StartPosition(*T).line <= End.line + 1) {
Expand Down
4 changes: 2 additions & 2 deletions clang-tools-extra/clangd/SourceCode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -814,8 +814,8 @@ llvm::SmallVector<llvm::StringRef> ancestorNamespaces(llvm::StringRef NS) {

// Checks whether \p FileName is a valid spelling of main file.
bool isMainFile(llvm::StringRef FileName, const SourceManager &SM) {
auto FE = SM.getFileManager().getFile(FileName);
return FE && *FE == SM.getFileEntryForID(SM.getMainFileID());
auto FE = SM.getFileManager().getOptionalFileRef(FileName);
return FE && FE == SM.getFileEntryRefForID(SM.getMainFileID());
}

} // namespace
Expand Down
1 change: 0 additions & 1 deletion clang-tools-extra/clangd/SystemIncludeExtractor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,6 @@ std::string convertGlobToRegex(llvm::StringRef Glob) {
}
}
RegStream << '$';
RegStream.flush();
return RegText;
}

Expand Down
1 change: 0 additions & 1 deletion clang-tools-extra/clangd/index/StdLib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ std::string buildUmbrella(llvm::StringLiteral Mandatory,
"#endif\n",
Header);
}
OS.flush();
return Result;
}

Expand Down
4 changes: 3 additions & 1 deletion clang-tools-extra/clangd/index/Symbol.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,11 @@ struct Symbol {
ImplementationDetail = 1 << 2,
/// Symbol is visible to other files (not e.g. a static helper function).
VisibleOutsideFile = 1 << 3,
/// Symbol has an attached documentation comment.
HasDocComment = 1 << 4
};

SymbolFlag Flags = SymbolFlag::None;

/// FIXME: also add deprecation message and fixit?
};

Expand Down
126 changes: 104 additions & 22 deletions clang-tools-extra/clangd/index/SymbolCollector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
Expand Down Expand Up @@ -75,18 +76,62 @@ bool isPrivateProtoDecl(const NamedDecl &ND) {
if (ND.getIdentifier() == nullptr)
return false;
auto Name = ND.getIdentifier()->getName();
if (!Name.contains('_'))
return false;
// Nested proto entities (e.g. Message::Nested) have top-level decls
// that shouldn't be used (Message_Nested). Ignore them completely.
// The nested entities are dangling type aliases, we may want to reconsider
// including them in the future.
// For enum constants, SOME_ENUM_CONSTANT is not private and should be
// indexed. Outer_INNER is private. This heuristic relies on naming style, it
// will include OUTER_INNER and exclude some_enum_constant.
// FIXME: the heuristic relies on naming style (i.e. no underscore in
// user-defined names) and can be improved.
return (ND.getKind() != Decl::EnumConstant) || llvm::any_of(Name, islower);
// There are some internal helpers like _internal_set_foo();
if (Name.contains("_internal_"))
return true;

// https://protobuf.dev/reference/cpp/cpp-generated/#nested-types
// Nested entities (messages/enums) has two names, one at the top-level scope,
// with a mangled name created by prepending all the outer types. These names
// are almost never preferred by the developers, so exclude them from index.
// e.g.
// message Foo {
// message Bar {}
// enum E { A }
// }
//
// yields:
// class Foo_Bar {};
// enum Foo_E { Foo_E_A };
// class Foo {
// using Bar = Foo_Bar;
// static constexpr Foo_E A = Foo_E_A;
// };

// We get rid of Foo_Bar and Foo_E by discarding any top-level entries with
// `_` in the name. This relies on original message/enum not having `_` in the
// name. Hence might go wrong in certain cases.
if (ND.getDeclContext()->isNamespace()) {
// Strip off some known public suffix helpers for enums, rest of the helpers
// are generated inside record decls so we don't care.
// https://protobuf.dev/reference/cpp/cpp-generated/#enum
Name.consume_back("_descriptor");
Name.consume_back("_IsValid");
Name.consume_back("_Name");
Name.consume_back("_Parse");
Name.consume_back("_MIN");
Name.consume_back("_MAX");
Name.consume_back("_ARRAYSIZE");
return Name.contains('_');
}

// EnumConstantDecls need some special attention, despite being nested in a
// TagDecl, they might still have mangled names. We filter those by checking
// if it has parent's name as a prefix.
// This might go wrong if a nested entity has a name that starts with parent's
// name, e.g: enum Foo { Foo_X }.
if (llvm::isa<EnumConstantDecl>(&ND)) {
auto *DC = llvm::cast<EnumDecl>(ND.getDeclContext());
if (!DC || !DC->getIdentifier())
return false;
auto CtxName = DC->getIdentifier()->getName();
return !CtxName.empty() && Name.consume_front(CtxName) &&
Name.consume_front("_");
}

// Now we're only left with fields/methods without an `_internal_` in the
// name, they're intended for public use.
return false;
}

// We only collect #include paths for symbols that are suitable for global code
Expand Down Expand Up @@ -635,17 +680,21 @@ bool SymbolCollector::handleDeclOccurrence(
return true;

const Symbol *BasicSymbol = Symbols.find(ID);
if (isPreferredDeclaration(*OriginalDecl, Roles))
bool SkipDocCheckInDef = false;
if (isPreferredDeclaration(*OriginalDecl, Roles)) {
// If OriginalDecl is preferred, replace/create the existing canonical
// declaration (e.g. a class forward declaration). There should be at most
// one duplicate as we expect to see only one preferred declaration per
// TU, because in practice they are definitions.
BasicSymbol = addDeclaration(*OriginalDecl, std::move(ID), IsMainFileOnly);
else if (!BasicSymbol || DeclIsCanonical)
SkipDocCheckInDef = true;
} else if (!BasicSymbol || DeclIsCanonical) {
BasicSymbol = addDeclaration(*ND, std::move(ID), IsMainFileOnly);
SkipDocCheckInDef = true;
}

if (Roles & static_cast<unsigned>(index::SymbolRole::Definition))
addDefinition(*OriginalDecl, *BasicSymbol);
addDefinition(*OriginalDecl, *BasicSymbol, SkipDocCheckInDef);

return true;
}
Expand Down Expand Up @@ -1025,16 +1074,28 @@ const Symbol *SymbolCollector::addDeclaration(const NamedDecl &ND, SymbolID ID,
*ASTCtx, *PP, CodeCompletionContext::CCC_Symbol, *CompletionAllocator,
*CompletionTUInfo,
/*IncludeBriefComments*/ false);
std::string Documentation =
formatDocumentation(*CCS, getDocComment(Ctx, SymbolCompletion,
/*CommentsFromHeaders=*/true));
std::string DocComment;
std::string Documentation;
bool AlreadyHasDoc = S.Flags & Symbol::HasDocComment;
if (!AlreadyHasDoc) {
DocComment = getDocComment(Ctx, SymbolCompletion,
/*CommentsFromHeaders=*/true);
Documentation = formatDocumentation(*CCS, DocComment);
}
const auto UpdateDoc = [&] {
if (!AlreadyHasDoc) {
if (!DocComment.empty())
S.Flags |= Symbol::HasDocComment;
S.Documentation = Documentation;
}
};
if (!(S.Flags & Symbol::IndexedForCodeCompletion)) {
if (Opts.StoreAllDocumentation)
S.Documentation = Documentation;
UpdateDoc();
Symbols.insert(S);
return Symbols.find(S.ID);
}
S.Documentation = Documentation;
UpdateDoc();
std::string Signature;
std::string SnippetSuffix;
getSignature(*CCS, &Signature, &SnippetSuffix, SymbolCompletion.Kind,
Expand All @@ -1058,8 +1119,8 @@ const Symbol *SymbolCollector::addDeclaration(const NamedDecl &ND, SymbolID ID,
return Symbols.find(S.ID);
}

void SymbolCollector::addDefinition(const NamedDecl &ND,
const Symbol &DeclSym) {
void SymbolCollector::addDefinition(const NamedDecl &ND, const Symbol &DeclSym,
bool SkipDocCheck) {
if (DeclSym.Definition)
return;
const auto &SM = ND.getASTContext().getSourceManager();
Expand All @@ -1074,6 +1135,27 @@ void SymbolCollector::addDefinition(const NamedDecl &ND,
Symbol S = DeclSym;
// FIXME: use the result to filter out symbols.
S.Definition = *DefLoc;

std::string DocComment;
std::string Documentation;
if (!SkipDocCheck && !(S.Flags & Symbol::HasDocComment) &&
(llvm::isa<FunctionDecl>(ND) || llvm::isa<CXXMethodDecl>(ND))) {
CodeCompletionResult SymbolCompletion(&getTemplateOrThis(ND), 0);
const auto *CCS = SymbolCompletion.CreateCodeCompletionString(
*ASTCtx, *PP, CodeCompletionContext::CCC_Symbol, *CompletionAllocator,
*CompletionTUInfo,
/*IncludeBriefComments*/ false);
DocComment = getDocComment(ND.getASTContext(), SymbolCompletion,
/*CommentsFromHeaders=*/true);
if (!S.Documentation.empty())
Documentation = S.Documentation.str() + '\n' + DocComment;
else
Documentation = formatDocumentation(*CCS, DocComment);
if (!DocComment.empty())
S.Flags |= Symbol::HasDocComment;
S.Documentation = Documentation;
}

Symbols.insert(S);
}

Expand Down
3 changes: 2 additions & 1 deletion clang-tools-extra/clangd/index/SymbolCollector.h
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,8 @@ class SymbolCollector : public index::IndexDataConsumer {
private:
const Symbol *addDeclaration(const NamedDecl &, SymbolID,
bool IsMainFileSymbol);
void addDefinition(const NamedDecl &, const Symbol &DeclSymbol);
void addDefinition(const NamedDecl &, const Symbol &DeclSymbol,
bool SkipDocCheck);
void processRelations(const NamedDecl &ND, const SymbolID &ID,
ArrayRef<index::SymbolRelation> Relations);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@
//
//===----------------------------------------------------------------------===//

#include "clang-pseudo/Bracket.h"
#include "Bracket.h"

namespace clang {
namespace pseudo {
namespace clangd {
namespace {

struct Bracket {
Expand All @@ -83,7 +83,7 @@ struct Bracket {
// Find brackets in the stream and convert to Bracket struct.
std::vector<Bracket> findBrackets(const TokenStream &Stream) {
std::vector<Bracket> Brackets;
auto Add = [&](const pseudo::Token &Tok, Bracket::BracketKind K,
auto Add = [&](const Token &Tok, Bracket::BracketKind K,
Bracket::Direction D) {
Brackets.push_back(
{K, D, Tok.Line, Tok.Indent, Stream.index(Tok), Bracket::None});
Expand Down Expand Up @@ -151,5 +151,5 @@ void pairBrackets(TokenStream &Stream) {
applyPairings(Brackets, Stream);
}

} // namespace pseudo
} // namespace clangd
} // namespace clang
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,19 @@
//
//===----------------------------------------------------------------------===//

#ifndef CLANG_PSEUDO_BRACKET_H
#define CLANG_PSEUDO_BRACKET_H
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_BRACKET_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_BRACKET_H

#include "clang-pseudo/Token.h"
#include "Token.h"

namespace clang {
namespace pseudo {
namespace clangd {

/// Identifies bracket token in the stream which should be paired.
/// Sets Token::Pair accordingly.
void pairBrackets(TokenStream &);

} // namespace pseudo
} // namespace clangd
} // namespace clang

#endif
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_BRACKET_H
10 changes: 10 additions & 0 deletions clang-tools-extra/clangd/support/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ if(NOT HAVE_CXX_ATOMICS_WITHOUT_LIB OR NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB)
endif()

add_clang_library(clangdSupport
Bracket.cpp
Cancellation.cpp
Context.cpp
DirectiveTree.cpp
FileCache.cpp
Lex.cpp
Logger.cpp
Markup.cpp
MemoryTree.cpp
Expand All @@ -27,9 +30,16 @@ add_clang_library(clangdSupport
ThreadCrashReporter.cpp
Threading.cpp
ThreadsafeFS.cpp
Token.cpp
Trace.cpp

LINK_LIBS
${LLVM_PTHREAD_LIB}
${CLANGD_ATOMIC_LIB}
)

clang_target_link_libraries(clangdSupport
PRIVATE
clangBasic
clangLex
)
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
//
//===----------------------------------------------------------------------===//

#include "clang-pseudo/DirectiveTree.h"
#include "DirectiveTree.h"
#include "clang/Basic/IdentifierTable.h"
#include "clang/Basic/TokenKinds.h"
#include "llvm/Support/FormatVariadic.h"
#include <optional>
#include <variant>

namespace clang {
namespace pseudo {
namespace clangd {
namespace {

class DirectiveParser {
Expand Down Expand Up @@ -353,5 +353,5 @@ TokenStream DirectiveTree::stripDirectives(const TokenStream &In) const {
return Out;
}

} // namespace pseudo
} // namespace clangd
} // namespace clang
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,17 @@
//
//===----------------------------------------------------------------------===//

#ifndef CLANG_PSEUDO_DIRECTIVETREE_H
#define CLANG_PSEUDO_DIRECTIVETREE_H
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_DIRECTIVETREE_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_DIRECTIVETREE_H

#include "clang-pseudo/Token.h"
#include "Token.h"
#include "clang/Basic/TokenKinds.h"
#include <optional>
#include <variant>
#include <vector>

namespace clang {
namespace pseudo {
namespace clangd {

/// Describes the structure of a source file, as seen by the preprocessor.
///
Expand Down Expand Up @@ -124,7 +124,7 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &,
/// The choices are stored in Conditional::Taken nodes.
void chooseConditionalBranches(DirectiveTree &, const TokenStream &Code);

} // namespace pseudo
} // namespace clangd
} // namespace clang

#endif // CLANG_PSEUDO_DIRECTIVETREE_H
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_DIRECTIVETREE_H
Loading