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
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
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
25 changes: 15 additions & 10 deletions clang-tools-extra/clang-tidy/modernize/LoopConvertUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -777,7 +777,7 @@ bool ForLoopIndexUseVisitor::TraverseLambdaCapture(LambdaExpr *LE,
const LambdaCapture *C,
Expr *Init) {
if (C->capturesVariable()) {
const ValueDecl *VDecl = C->getCapturedVar();
ValueDecl *VDecl = C->getCapturedVar();
if (areSameVariable(IndexVar, VDecl)) {
// FIXME: if the index is captured, it will count as an usage and the
// alias (if any) won't work, because it is only used in case of having
Expand All @@ -787,6 +787,8 @@ bool ForLoopIndexUseVisitor::TraverseLambdaCapture(LambdaExpr *LE,
: Usage::UK_CaptureByRef,
C->getLocation()));
}
if (VDecl->isInitCapture())
TraverseStmtImpl(cast<VarDecl>(VDecl)->getInit());
}
return VisitorBase::TraverseLambdaCapture(LE, C, Init);
}
Expand Down Expand Up @@ -816,6 +818,17 @@ bool ForLoopIndexUseVisitor::VisitDeclStmt(DeclStmt *S) {
return true;
}

bool ForLoopIndexUseVisitor::TraverseStmtImpl(Stmt *S) {
// All this pointer swapping is a mechanism for tracking immediate parentage
// of Stmts.
const Stmt *OldNextParent = NextStmtParent;
CurrStmtParent = NextStmtParent;
NextStmtParent = S;
bool Result = VisitorBase::TraverseStmt(S);
NextStmtParent = OldNextParent;
return Result;
}

bool ForLoopIndexUseVisitor::TraverseStmt(Stmt *S) {
// If this is an initialization expression for a lambda capture, prune the
// traversal so that we don't end up diagnosing the contained DeclRefExpr as
Expand All @@ -828,15 +841,7 @@ bool ForLoopIndexUseVisitor::TraverseStmt(Stmt *S) {
return true;
}
}

// All this pointer swapping is a mechanism for tracking immediate parentage
// of Stmts.
const Stmt *OldNextParent = NextStmtParent;
CurrStmtParent = NextStmtParent;
NextStmtParent = S;
bool Result = VisitorBase::TraverseStmt(S);
NextStmtParent = OldNextParent;
return Result;
return TraverseStmtImpl(S);
}

std::string VariableNamer::createIndexName() {
Expand Down
2 changes: 2 additions & 0 deletions clang-tools-extra/clang-tidy/modernize/LoopConvertUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,8 @@ class ForLoopIndexUseVisitor
bool VisitDeclStmt(DeclStmt *S);
bool TraverseStmt(Stmt *S);

bool TraverseStmtImpl(Stmt *S);

/// Add an expression to the list of expressions on which the container
/// expression depends.
void addComponent(const Expr *E);
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
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
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
6 changes: 3 additions & 3 deletions clang-tools-extra/clangd/ParsedAST.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -342,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 @@ -585,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
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
69 changes: 57 additions & 12 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
3 changes: 3 additions & 0 deletions clang-tools-extra/clangd/tool/ClangdMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,9 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var
Opts.CodeComplete.EnableFunctionArgSnippets = EnableFunctionArgSnippets;
Opts.CodeComplete.RunParser = CodeCompletionParse;
Opts.CodeComplete.RankingModel = RankingModel;
// FIXME: If we're using C++20 modules, force the lookup process to load
// external decls, since currently the index doesn't support C++20 modules.
Opts.CodeComplete.ForceLoadPreamble = ExperimentalModulesSupport;

RealThreadsafeFS TFS;
std::vector<std::unique_ptr<config::Provider>> ProviderStack;
Expand Down
47 changes: 13 additions & 34 deletions clang-tools-extra/clangd/unittests/ConfigCompileTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -298,41 +298,20 @@ TEST_F(ConfigCompileTests, DiagnosticSuppression) {
"unreachable-code", "unused-variable",
"typecheck_bool_condition",
"unexpected_friend", "warn_alloca"));
clang::DiagnosticsEngine DiagEngine(new DiagnosticIDs, nullptr,
new clang::IgnoringDiagConsumer);

using Diag = clang::Diagnostic;
{
auto D = DiagEngine.Report(diag::warn_unreachable);
EXPECT_TRUE(isDiagnosticSuppressed(
Diag{&DiagEngine, D}, Conf.Diagnostics.Suppress, LangOptions()));
}
EXPECT_TRUE(isBuiltinDiagnosticSuppressed(
diag::warn_unreachable, Conf.Diagnostics.Suppress, LangOptions()));
// Subcategory not respected/suppressed.
{
auto D = DiagEngine.Report(diag::warn_unreachable_break);
EXPECT_FALSE(isDiagnosticSuppressed(
Diag{&DiagEngine, D}, Conf.Diagnostics.Suppress, LangOptions()));
}
{
auto D = DiagEngine.Report(diag::warn_unused_variable);
EXPECT_TRUE(isDiagnosticSuppressed(
Diag{&DiagEngine, D}, Conf.Diagnostics.Suppress, LangOptions()));
}
{
auto D = DiagEngine.Report(diag::err_typecheck_bool_condition);
EXPECT_TRUE(isDiagnosticSuppressed(
Diag{&DiagEngine, D}, Conf.Diagnostics.Suppress, LangOptions()));
}
{
auto D = DiagEngine.Report(diag::err_unexpected_friend);
EXPECT_TRUE(isDiagnosticSuppressed(
Diag{&DiagEngine, D}, Conf.Diagnostics.Suppress, LangOptions()));
}
{
auto D = DiagEngine.Report(diag::warn_alloca);
EXPECT_TRUE(isDiagnosticSuppressed(
Diag{&DiagEngine, D}, Conf.Diagnostics.Suppress, LangOptions()));
}
EXPECT_FALSE(isBuiltinDiagnosticSuppressed(
diag::warn_unreachable_break, Conf.Diagnostics.Suppress, LangOptions()));
EXPECT_TRUE(isBuiltinDiagnosticSuppressed(
diag::warn_unused_variable, Conf.Diagnostics.Suppress, LangOptions()));
EXPECT_TRUE(isBuiltinDiagnosticSuppressed(diag::err_typecheck_bool_condition,
Conf.Diagnostics.Suppress,
LangOptions()));
EXPECT_TRUE(isBuiltinDiagnosticSuppressed(
diag::err_unexpected_friend, Conf.Diagnostics.Suppress, LangOptions()));
EXPECT_TRUE(isBuiltinDiagnosticSuppressed(
diag::warn_alloca, Conf.Diagnostics.Suppress, LangOptions()));

Frag.Diagnostics.Suppress.emplace_back("*");
EXPECT_TRUE(compileAndApply());
Expand Down
24 changes: 24 additions & 0 deletions clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1984,6 +1984,30 @@ TEST(Diagnostics, Tags) {
withTag(DiagnosticTag::Deprecated)))));
}

TEST(Diagnostics, TidyDiagsArentAffectedFromWerror) {
TestTU TU;
TU.ExtraArgs = {"-Werror"};
Annotations Test(R"cpp($typedef[[typedef int INT]]; // error-ok)cpp");
TU.Code = Test.code().str();
TU.ClangTidyProvider = addTidyChecks("modernize-use-using");
EXPECT_THAT(
TU.build().getDiagnostics(),
ifTidyChecks(UnorderedElementsAre(
AllOf(Diag(Test.range("typedef"), "use 'using' instead of 'typedef'"),
// Make sure severity for clang-tidy finding isn't bumped to
// error due to Werror in compile flags.
diagSeverity(DiagnosticsEngine::Warning)))));

TU.ClangTidyProvider =
addTidyChecks("modernize-use-using", /*WarningsAsErrors=*/"modernize-*");
EXPECT_THAT(
TU.build().getDiagnostics(),
ifTidyChecks(UnorderedElementsAre(
AllOf(Diag(Test.range("typedef"), "use 'using' instead of 'typedef'"),
// Unless bumped explicitly with WarnAsError.
diagSeverity(DiagnosticsEngine::Error)))));
}

TEST(Diagnostics, DeprecatedDiagsAreHints) {
ClangdDiagnosticOptions Opts;
std::optional<clangd::Diagnostic> Diag;
Expand Down
4 changes: 2 additions & 2 deletions clang-tools-extra/clangd/unittests/ParsedASTTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -397,10 +397,10 @@ TEST(ParsedASTTest, PatchesAdditionalIncludes) {
auto &FM = SM.getFileManager();
// Copy so that we can use operator[] to get the children.
IncludeStructure Includes = PatchedAST->getIncludeStructure();
auto MainFE = FM.getFile(testPath("foo.cpp"));
auto MainFE = FM.getOptionalFileRef(testPath("foo.cpp"));
ASSERT_TRUE(MainFE);
auto MainID = Includes.getID(*MainFE);
auto AuxFE = FM.getFile(testPath("sub/aux.h"));
auto AuxFE = FM.getOptionalFileRef(testPath("sub/aux.h"));
ASSERT_TRUE(AuxFE);
auto AuxID = Includes.getID(*AuxFE);
EXPECT_THAT(Includes.IncludeChildren[*MainID], Contains(*AuxID));
Expand Down
80 changes: 80 additions & 0 deletions clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,86 @@ import A;
EXPECT_TRUE(D.isFromASTFile());
}

// An end to end test for code complete in modules
TEST_F(PrerequisiteModulesTests, CodeCompleteTest) {
MockDirectoryCompilationDatabase CDB(TestDir, FS);

CDB.addFile("A.cppm", R"cpp(
export module A;
export void printA();
)cpp");

llvm::StringLiteral UserContents = R"cpp(
import A;
void func() {
print^
}
)cpp";

CDB.addFile("Use.cpp", UserContents);
Annotations Test(UserContents);

ModulesBuilder Builder(CDB);

ParseInputs Use = getInputs("Use.cpp", CDB);
Use.ModulesManager = &Builder;

std::unique_ptr<CompilerInvocation> CI =
buildCompilerInvocation(Use, DiagConsumer);
EXPECT_TRUE(CI);

auto Preamble =
buildPreamble(getFullPath("Use.cpp"), *CI, Use, /*InMemory=*/true,
/*Callback=*/nullptr);
EXPECT_TRUE(Preamble);
EXPECT_TRUE(Preamble->RequiredModules);

auto Result = codeComplete(getFullPath("Use.cpp"), Test.point(),
Preamble.get(), Use, {});
EXPECT_FALSE(Result.Completions.empty());
EXPECT_EQ(Result.Completions[0].Name, "printA");
}

TEST_F(PrerequisiteModulesTests, SignatureHelpTest) {
MockDirectoryCompilationDatabase CDB(TestDir, FS);

CDB.addFile("A.cppm", R"cpp(
export module A;
export void printA(int a);
)cpp");

llvm::StringLiteral UserContents = R"cpp(
import A;
void func() {
printA(^);
}
)cpp";

CDB.addFile("Use.cpp", UserContents);
Annotations Test(UserContents);

ModulesBuilder Builder(CDB);

ParseInputs Use = getInputs("Use.cpp", CDB);
Use.ModulesManager = &Builder;

std::unique_ptr<CompilerInvocation> CI =
buildCompilerInvocation(Use, DiagConsumer);
EXPECT_TRUE(CI);

auto Preamble =
buildPreamble(getFullPath("Use.cpp"), *CI, Use, /*InMemory=*/true,
/*Callback=*/nullptr);
EXPECT_TRUE(Preamble);
EXPECT_TRUE(Preamble->RequiredModules);

auto Result = signatureHelp(getFullPath("Use.cpp"), Test.point(),
*Preamble.get(), Use, MarkupKind::PlainText);
EXPECT_FALSE(Result.signatures.empty());
EXPECT_EQ(Result.signatures[0].label, "printA(int a) -> void");
EXPECT_EQ(Result.signatures[0].parameters[0].labelString, "int a");
}

} // namespace
} // namespace clang::clangd

Expand Down
64 changes: 54 additions & 10 deletions clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,19 +201,63 @@ TEST_F(ShouldCollectSymbolTest, NoPrivateProtoSymbol) {
build(
R"(// Generated by the protocol buffer compiler. DO NOT EDIT!
namespace nx {
class Top_Level {};
class TopLevel {};
enum Kind {
KIND_OK,
Kind_Not_Ok,
enum Outer_Enum : int {
Outer_Enum_KIND1,
Outer_Enum_Kind_2,
};
bool Outer_Enum_IsValid(int);
class Outer_Inner {};
class Outer {
using Inner = Outer_Inner;
using Enum = Outer_Enum;
static constexpr Enum KIND1 = Outer_Enum_KIND1;
static constexpr Enum Kind_2 = Outer_Enum_Kind_2;
static bool Enum_IsValid(int);
int &x();
void set_x();
void _internal_set_x();
int &Outer_y();
};
enum Foo {
FOO_VAL1,
Foo_VAL2,
};
bool Foo_IsValid(int);
})");
EXPECT_TRUE(shouldCollect("nx::TopLevel"));
EXPECT_TRUE(shouldCollect("nx::Kind::KIND_OK"));
EXPECT_TRUE(shouldCollect("nx::Kind"));

EXPECT_FALSE(shouldCollect("nx::Top_Level"));
EXPECT_FALSE(shouldCollect("nx::Kind::Kind_Not_Ok"));
// Make sure all the mangled names for Outer::Enum is discarded.
EXPECT_FALSE(shouldCollect("nx::Outer_Enum"));
EXPECT_FALSE(shouldCollect("nx::Outer_Enum_KIND1"));
EXPECT_FALSE(shouldCollect("nx::Outer_Enum_Kind_2"));
EXPECT_FALSE(shouldCollect("nx::Outer_Enum_IsValid"));
// But nested aliases are preserved.
EXPECT_TRUE(shouldCollect("nx::Outer::Enum"));
EXPECT_TRUE(shouldCollect("nx::Outer::KIND1"));
EXPECT_TRUE(shouldCollect("nx::Outer::Kind_2"));
EXPECT_TRUE(shouldCollect("nx::Outer::Enum_IsValid"));

// Check for Outer::Inner.
EXPECT_FALSE(shouldCollect("nx::Outer_Inner"));
EXPECT_TRUE(shouldCollect("nx::Outer"));
EXPECT_TRUE(shouldCollect("nx::Outer::Inner"));

// Make sure field related information is preserved, unless it's explicitly
// marked with `_internal_`.
EXPECT_TRUE(shouldCollect("nx::Outer::x"));
EXPECT_TRUE(shouldCollect("nx::Outer::set_x"));
EXPECT_FALSE(shouldCollect("nx::Outer::_internal_set_x"));
EXPECT_TRUE(shouldCollect("nx::Outer::Outer_y"));

// Handling of a top-level enum
EXPECT_TRUE(shouldCollect("nx::Foo::FOO_VAL1"));
EXPECT_TRUE(shouldCollect("nx::FOO_VAL1"));
EXPECT_TRUE(shouldCollect("nx::Foo_IsValid"));
// Our heuristic goes wrong here, if the user has a nested name that starts
// with parent's name.
EXPECT_FALSE(shouldCollect("nx::Foo::Foo_VAL2"));
EXPECT_FALSE(shouldCollect("nx::Foo_VAL2"));
}

TEST_F(ShouldCollectSymbolTest, DoubleCheckProtoHeaderComment) {
Expand Down
1 change: 0 additions & 1 deletion clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,6 @@ const NamedDecl &findDeclWithTemplateArgs(ParsedAST &AST,
// Use getNameForDiagnostic() which includes the template
// arguments in the printed name.
ND.getNameForDiagnostic(OS, Policy, /*Qualified=*/true);
OS.flush();
return QName == Query;
});
}
Expand Down
51 changes: 39 additions & 12 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@ Improvements to clang-tidy
New checks
^^^^^^^^^^

- New :doc:`bugprone-tagged-union-member-count
<clang-tidy/checks/bugprone/tagged-union-member-count>` check.

Gives warnings for tagged unions, where the number of tags is
different from the number of data members inside the union.

New check aliases
^^^^^^^^^^^^^^^^^

Expand All @@ -125,11 +131,24 @@ Changes in existing checks
<clang-tidy/checks/bugprone/forwarding-reference-overload>` check by fixing
a crash when determining if an ``enable_if[_t]`` was found.

- Improved :doc:`bugprone-posix-return
<clang-tidy/checks/bugprone/posix-return>` check to support integer literals
as LHS and posix call as RHS of comparison.

- Improved :doc:`bugprone-sizeof-expression
<clang-tidy/checks/bugprone/sizeof-expression>` check to find suspicious
usages of ``sizeof()``, ``alignof()``, and ``offsetof()`` when adding or
subtracting from a pointer.

- Improved :doc:`bugprone-unchecked-optional-access
<clang-tidy/checks/bugprone/unchecked-optional-access>` to support
`bsl::optional` and `bdlb::NullableValue` from
<https://github.com/bloomberg/bde>_.

- Improved :doc:`bugprone-unsafe-functions
<clang-tidy/checks/bugprone/unsafe-functions>` check to allow specifying
additional functions to match.

- Improved :doc:`cert-flp30-c <clang-tidy/checks/cert/flp30-c>` check to
fix false positive that floating point variable is only used in increment
expression.
Expand All @@ -143,33 +162,36 @@ Changes in existing checks
<clang-tidy/checks/misc/definitions-in-headers>` check by rewording the
diagnostic note that suggests adding ``inline``.

- Improved :doc:`misc-unconventional-assign-operator
<clang-tidy/checks/misc/unconventional-assign-operator>` check to avoid
false positive for C++23 deducing this.

- Improved :doc:`modernize-avoid-c-arrays
<clang-tidy/checks/modernize/avoid-c-arrays>` check to suggest using ``std::span``
as a replacement for parameters of incomplete C array type in C++20 and
``std::array`` or ``std::vector`` before C++20.

- Improved :doc:`modernize-use-std-format
<clang-tidy/checks/modernize/use-std-format>` check to support replacing
member function calls too.

- Improved :doc:`misc-unconventional-assign-operator
<clang-tidy/checks/misc/unconventional-assign-operator>` check to avoid
false positive for C++23 deducing this.
- Improved :doc:`modernize-loop-convert
<clang-tidy/checks/modernize/loop-convert>` check to fix false positive when
using loop variable in initializer of lambda capture.

- Improved :doc:`modernize-min-max-use-initializer-list
<clang-tidy/checks/modernize/min-max-use-initializer-list>` check by fixing
a false positive when only an implicit conversion happened inside an
initializer list.

- Improved :doc:`modernize-use-nullptr
<clang-tidy/checks/modernize/use-nullptr>` check to also recognize
``NULL``/``__null`` (but not ``0``) when used with a templated type.

- Improved :doc:`modernize-use-std-format
<clang-tidy/checks/modernize/use-std-format>` check to support replacing
member function calls too.

- Improved :doc:`modernize-use-std-print
<clang-tidy/checks/modernize/use-std-print>` check to support replacing
member function calls too.

- Improved :doc:`readability-enum-initial-value
<clang-tidy/checks/readability/enum-initial-value>` check by only issuing
diagnostics for the definition of an ``enum``, and by fixing a typo in the
diagnostic.

- Improved :doc:`performance-avoid-endl
<clang-tidy/checks/performance/avoid-endl>` check to use ``std::endl`` as
placeholder when lexer cannot get source text.
Expand All @@ -178,6 +200,11 @@ Changes in existing checks
<clang-tidy/checks/readability/container-contains>` check to let it work on
any class that has a ``contains`` method.

- Improved :doc:`readability-enum-initial-value
<clang-tidy/checks/readability/enum-initial-value>` check by only issuing
diagnostics for the definition of an ``enum``, and by fixing a typo in the
diagnostic.

- Improved :doc:`readability-implicit-bool-conversion
<clang-tidy/checks/readability/implicit-bool-conversion>` check
by adding the option `UseUpperCaseLiteralSuffix` to select the
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
.. title:: clang-tidy - bugprone-tagged-union-member-count

bugprone-tagged-union-member-count
==================================

Gives warnings for tagged unions, where the number of tags is
different from the number of data members inside the union.

A struct or a class is considered to be a tagged union if it has
exactly one union data member and exactly one enum data member and
any number of other data members that are neither unions or enums.

Example:

.. code-block:: c++

enum Tags {
Tag1,
Tag2,
};

struct TaggedUnion { // warning: tagged union has more data members (3) than tags (2)
enum Tags Kind;
union {
int I;
float F;
char *Str;
} Data;
};
How enum constants are counted
------------------------------

The main complicating factor when counting the number of enum constants is that
some of them might be auxiliary values that purposefully don't have a corresponding union
data member and are used for something else. For example the last enum constant
sometimes explicitly "points to" the last declared valid enum constant or
tracks how many enum constants have been declared.

For an illustration:

.. code-block:: c++

enum TagWithLast {
Tag1 = 0,
Tag2 = 1,
Tag3 = 2,
LastTag = 2
};

enum TagWithCounter {
Tag1, // is 0
Tag2, // is 1
Tag3, // is 2
TagCount, // is 3
};

The check counts the number of distinct values among the enum constants and not the enum
constants themselves. This way the enum constants that are essentially just aliases of other
enum constants are not included in the final count.

Handling of counting enum constants (ones like :code:`TagCount` in the previous code example)
is done by decreasing the number of enum values by one if the name of the last enum constant
starts with a prefix or ends with a suffix specified in :option:`CountingEnumPrefixes`,
:option:`CountingEnumSuffixes` and it's value is one less than the total number of distinct
values in the enum.

When the final count is adjusted based on this heuristic then a diagnostic note is emitted
that shows which enum constant matched the criteria.

The heuristic can be disabled entirely (:option:`EnableCountingEnumHeuristic`) or
configured to follow your naming convention (:option:`CountingEnumPrefixes`, :option:`CountingEnumSuffixes`).
The strings specified in :option:`CountingEnumPrefixes`, :option:`CountingEnumSuffixes` are matched
case insensitively.

Example counts:

.. code-block:: c++

// Enum count is 3, because the value 2 is counted only once
enum TagWithLast {
Tag1 = 0,
Tag2 = 1,
Tag3 = 2,
LastTag = 2
};

// Enum count is 3, because TagCount is heuristically excluded
enum TagWithCounter {
Tag1, // is 0
Tag2, // is 1
Tag3, // is 2
TagCount, // is 3
};


Options
-------

.. option:: EnableCountingEnumHeuristic

This option enables or disables the counting enum heuristic.
It uses the prefixes and suffixes specified in the options
:option:`CountingEnumPrefixes`, :option:`CountingEnumSuffixes` to find counting enum constants by
using them for prefix and suffix matching.

This option is enabled by default.

When :option:`EnableCountingEnumHeuristic` is `false`:

.. code-block:: c++

enum TagWithCounter {
Tag1,
Tag2,
Tag3,
TagCount,
};

struct TaggedUnion {
TagWithCounter Kind;
union {
int A;
long B;
char *Str;
float F;
} Data;
};
When :option:`EnableCountingEnumHeuristic` is `true`:

.. code-block:: c++

enum TagWithCounter {
Tag1,
Tag2,
Tag3,
TagCount,
};

struct TaggedUnion { // warning: tagged union has more data members (4) than tags (3)
TagWithCounter Kind;
union {
int A;
long B;
char *Str;
float F;
} Data;
};
.. option:: CountingEnumPrefixes

See :option:`CountingEnumSuffixes` below.

.. option:: CountingEnumSuffixes

CountingEnumPrefixes and CountingEnumSuffixes are lists of semicolon
separated strings that are used to search for possible counting enum constants.
These strings are matched case insensitively as prefixes and suffixes
respectively on the names of the enum constants.
If :option:`EnableCountingEnumHeuristic` is `false` then these options do nothing.

The default value of :option:`CountingEnumSuffixes` is `count` and of
:option:`CountingEnumPrefixes` is the empty string.

When :option:`EnableCountingEnumHeuristic` is `true` and :option:`CountingEnumSuffixes`
is `count;size`:

.. code-block:: c++

enum TagWithCounterCount {
Tag1,
Tag2,
Tag3,
TagCount,
};

struct TaggedUnionCount { // warning: tagged union has more data members (4) than tags (3)
TagWithCounterCount Kind;
union {
int A;
long B;
char *Str;
float F;
} Data;
};
enum TagWithCounterSize {
Tag11,
Tag22,
Tag33,
TagSize,
};

struct TaggedUnionSize { // warning: tagged union has more data members (4) than tags (3)
TagWithCounterSize Kind;
union {
int A;
long B;
char *Str;
float F;
} Data;
};
When :option:`EnableCountingEnumHeuristic` is `true` and :option:`CountingEnumPrefixes` is `maxsize;last_`

.. code-block:: c++

enum TagWithCounterLast {
Tag1,
Tag2,
Tag3,
last_tag,
};

struct TaggedUnionLast { // warning: tagged union has more data members (4) than tags (3)
TagWithCounterLast tag;
union {
int I;
short S;
char *C;
float F;
} Data;
};
enum TagWithCounterMaxSize {
Tag1,
Tag2,
Tag3,
MaxSizeTag,
};

struct TaggedUnionMaxSize { // warning: tagged union has more data members (4) than tags (3)
TagWithCounterMaxSize tag;
union {
int I;
short S;
char *C;
float F;
} Data;
};
.. option:: StrictMode

When enabled, the check will also give a warning, when the number of tags
is greater than the number of union data members.

This option is disabled by default.

When :option:`StrictMode` is `false`:

.. code-block:: c++

struct TaggedUnion {
enum {
Tag1,
Tag2,
Tag3,
} Tags;
union {
int I;
float F;
} Data;
};

When :option:`StrictMode` is `true`:

.. code-block:: c++

struct TaggedUnion { // warning: tagged union has fewer data members (2) than tags (3)
enum {
Tag1,
Tag2,
Tag3,
} Tags;
union {
int I;
float F;
} Data;
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ results. Therefore, it may be more resource intensive (RAM, CPU) than the
average clang-tidy check.

This check identifies unsafe accesses to values contained in
``std::optional<T>``, ``absl::optional<T>``, ``base::Optional<T>``, or
``folly::Optional<T>`` objects. Below we will refer to all these types
collectively as ``optional<T>``.
``std::optional<T>``, ``absl::optional<T>``, ``base::Optional<T>``,
``folly::Optional<T>``, ``bsl::optional``, or
``BloombergLP::bdlb::NullableValue`` objects. Below we will refer to all these
types collectively as ``optional<T>``.

An access to the value of an ``optional<T>`` occurs when one of its ``value``,
``operator*``, or ``operator->`` member functions is invoked. To align with
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ The check implements the following rules from the CERT C Coding Standard:
Unsafe functions
----------------

The following functions are reported if :option:`ReportDefaultFunctions` is enabled.

If *Annex K.* is available, a replacement from *Annex K.* is suggested for the
following functions:

Expand All @@ -45,8 +47,7 @@ The following functions are always checked, regardless of *Annex K* availability
- ``rewind``, suggested replacement: ``fseek``
- ``setbuf``, suggested replacement: ``setvbuf``

If `ReportMoreUnsafeFunctions
<unsafe-functions.html#cmdoption-arg-ReportMoreUnsafeFunctions>`_ is enabled,
If :option:`ReportMoreUnsafeFunctions` is enabled,
the following functions are also checked:

- ``bcmp``, suggested replacement: ``memcmp``
Expand Down Expand Up @@ -74,6 +75,44 @@ Both macros have to be defined to suggest replacement functions from *Annex K.*
``__STDC_WANT_LIB_EXT1__`` must be defined to ``1`` by the user **before**
including any system headers.

.. _CustomFunctions:

Custom functions
----------------

The option :option:`CustomFunctions` allows the user to define custom functions to be
checked. The format is the following, without newlines:

.. code::
bugprone-unsafe-functions.CustomFunctions="
functionRegex1[, replacement1[, reason1]];
functionRegex2[, replacement2[, reason2]];
...
"
The functions are matched using POSIX extended regular expressions.
*(Note: The regular expressions do not support negative* ``(?!)`` *matches.)*

The `reason` is optional and is used to provide additional information about the
reasoning behind the replacement. The default reason is `is marked as unsafe`.

If `replacement` is empty, the text `it should not be used` will be shown
instead of the suggestion for a replacement.

As an example, the configuration `^original$, replacement, is deprecated;`
will produce the following diagnostic message.

.. code:: c
original(); // warning: function 'original' is deprecated; 'replacement' should be used instead.
::std::original(); // no-warning
original_function(); // no-warning
If the regular expression contains the character `:`, it is matched against the
qualified name (i.e. ``std::original``), otherwise the regex is matched against the unqualified name (``original``).
If the regular expression starts with `::` (or `^::`), it is matched against the
fully qualified name (``::std::original``).

Options
-------
Expand All @@ -86,6 +125,19 @@ Options
this option enables.
Default is `true`.

.. option:: ReportDefaultFunctions

When `true`, the check reports the default set of functions.
Consider changing the setting to false if you only want to see custom
functions matched via :ref:`custom functions<CustomFunctions>`.
Default is `true`.

.. option:: CustomFunctions

A semicolon-separated list of custom functions to be matched. A matched
function contains a regular expression, an optional name of the replacement
function, and an optional reason, separated by comma. For more information,
see :ref:`Custom functions<CustomFunctions>`.

Examples
--------
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/docs/clang-tidy/checks/list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ Clang-Tidy Checks
:doc:`bugprone-suspicious-stringview-data-usage <bugprone/suspicious-stringview-data-usage>`,
:doc:`bugprone-swapped-arguments <bugprone/swapped-arguments>`, "Yes"
:doc:`bugprone-switch-missing-default-case <bugprone/switch-missing-default-case>`,
:doc:`bugprone-tagged-union-member-count <bugprone/tagged-union-member-count>`,
:doc:`bugprone-terminating-continue <bugprone/terminating-continue>`, "Yes"
:doc:`bugprone-throw-keyword-missing <bugprone/throw-keyword-missing>`,
:doc:`bugprone-too-small-loop-variable <bugprone/too-small-loop-variable>`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include <string>
#include <utility>

namespace clang {
class SourceLocation;
Expand Down Expand Up @@ -62,7 +63,8 @@ void walkUsed(llvm::ArrayRef<Decl *> ASTRoots,

struct AnalysisResults {
std::vector<const Include *> Unused;
std::vector<std::string> Missing; // Spellings, like "<vector>"
// Spellings, like "<vector>" paired with the Header that generated it.
std::vector<std::pair<std::string, Header>> Missing;
};

/// Determine which headers should be inserted or removed from the main file.
Expand Down
16 changes: 8 additions & 8 deletions clang-tools-extra/include-cleaner/lib/Analysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/STLFunctionalExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/ErrorHandling.h"
#include <cassert>
Expand Down Expand Up @@ -84,7 +84,7 @@ analyze(llvm::ArrayRef<Decl *> ASTRoots,
auto &SM = PP.getSourceManager();
const auto MainFile = *SM.getFileEntryRefForID(SM.getMainFileID());
llvm::DenseSet<const Include *> Used;
llvm::StringSet<> Missing;
llvm::StringMap<Header> Missing;
if (!HeaderFilter)
HeaderFilter = [](llvm::StringRef) { return false; };
OptionalDirectoryEntryRef ResourceDir =
Expand Down Expand Up @@ -119,7 +119,7 @@ analyze(llvm::ArrayRef<Decl *> ASTRoots,
Satisfied = true;
}
if (!Satisfied)
Missing.insert(std::move(Spelling));
Missing.try_emplace(std::move(Spelling), Providers.front());
});

AnalysisResults Results;
Expand All @@ -144,8 +144,8 @@ analyze(llvm::ArrayRef<Decl *> ASTRoots,
}
Results.Unused.push_back(&I);
}
for (llvm::StringRef S : Missing.keys())
Results.Missing.push_back(S.str());
for (auto &E : Missing)
Results.Missing.emplace_back(E.first().str(), E.second);
llvm::sort(Results.Missing);
return Results;
}
Expand All @@ -158,9 +158,9 @@ std::string fixIncludes(const AnalysisResults &Results,
// Encode insertions/deletions in the magic way clang-format understands.
for (const Include *I : Results.Unused)
cantFail(R.add(tooling::Replacement(FileName, UINT_MAX, 1, I->quote())));
for (llvm::StringRef Spelled : Results.Missing)
cantFail(R.add(tooling::Replacement(FileName, UINT_MAX, 0,
("#include " + Spelled).str())));
for (auto &[Spelled, _] : Results.Missing)
cantFail(R.add(
tooling::Replacement(FileName, UINT_MAX, 0, "#include " + Spelled)));
// "cleanup" actually turns the UINT_MAX replacements into concrete edits.
auto Positioned = cantFail(format::cleanupAroundReplacements(Code, R, Style));
return cantFail(tooling::applyAllReplacements(Code, Positioned));
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/include-cleaner/tool/IncludeCleaner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ class Action : public clang::ASTFrontendAction {
case PrintStyle::Changes:
for (const Include *I : Results.Unused)
llvm::outs() << "- " << I->quote() << " @Line:" << I->Line << "\n";
for (const auto &I : Results.Missing)
for (const auto &[I, _] : Results.Missing)
llvm::outs() << "+ " << I << "\n";
break;
case PrintStyle::Final:
Expand Down
20 changes: 12 additions & 8 deletions clang-tools-extra/include-cleaner/unittests/AnalysisTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "llvm/ADT/IntrusiveRefCntPtr.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/ScopedPrinter.h"
#include "llvm/Support/VirtualFileSystem.h"
Expand All @@ -39,6 +40,7 @@

namespace clang::include_cleaner {
namespace {
using testing::_;
using testing::AllOf;
using testing::Contains;
using testing::ElementsAre;
Expand Down Expand Up @@ -262,10 +264,12 @@ int x = a + c;
auto Results =
analyze(std::vector<Decl *>{Decls.begin(), Decls.end()},
PP.MacroReferences, PP.Includes, &PI, AST.preprocessor());
auto CHeader = llvm::cantFail(
AST.context().getSourceManager().getFileManager().getFileRef("c.h"));

const Include *B = PP.Includes.atLine(3);
ASSERT_EQ(B->Spelled, "b.h");
EXPECT_THAT(Results.Missing, ElementsAre("\"c.h\""));
EXPECT_THAT(Results.Missing, ElementsAre(Pair("\"c.h\"", Header(CHeader))));
EXPECT_THAT(Results.Unused, ElementsAre(B));
}

Expand Down Expand Up @@ -370,7 +374,7 @@ TEST_F(AnalyzeTest, SpellingIncludesWithSymlinks) {
auto Results = analyze(DeclsInTU, {}, PP.Includes, &PI, AST.preprocessor());
// Check that we're spelling header using the symlink, and not underlying
// path.
EXPECT_THAT(Results.Missing, testing::ElementsAre("\"inner.h\""));
EXPECT_THAT(Results.Missing, testing::ElementsAre(Pair("\"inner.h\"", _)));
// header.h should be unused.
EXPECT_THAT(Results.Unused, Not(testing::IsEmpty()));

Expand All @@ -379,7 +383,7 @@ TEST_F(AnalyzeTest, SpellingIncludesWithSymlinks) {
auto HeaderFilter = [](llvm::StringRef Path) { return Path == "inner.h"; };
Results = analyze(DeclsInTU, {}, PP.Includes, &PI, AST.preprocessor(),
HeaderFilter);
EXPECT_THAT(Results.Missing, testing::ElementsAre("\"inner.h\""));
EXPECT_THAT(Results.Missing, testing::ElementsAre(Pair("\"inner.h\"", _)));
// header.h should be unused.
EXPECT_THAT(Results.Unused, Not(testing::IsEmpty()));
}
Expand All @@ -389,7 +393,7 @@ TEST_F(AnalyzeTest, SpellingIncludesWithSymlinks) {
HeaderFilter);
// header.h should be ignored now.
EXPECT_THAT(Results.Unused, Not(testing::IsEmpty()));
EXPECT_THAT(Results.Missing, testing::ElementsAre("\"inner.h\""));
EXPECT_THAT(Results.Missing, testing::ElementsAre(Pair("\"inner.h\"", _)));
}
}

Expand All @@ -414,9 +418,9 @@ TEST(FixIncludes, Basic) {
Inc.add(I);

AnalysisResults Results;
Results.Missing.push_back("\"aa.h\"");
Results.Missing.push_back("\"ab.h\"");
Results.Missing.push_back("<e.h>");
Results.Missing.emplace_back("\"aa.h\"", Header(""));
Results.Missing.emplace_back("\"ab.h\"", Header(""));
Results.Missing.emplace_back("<e.h>", Header(""));
Results.Unused.push_back(Inc.atLine(3));
Results.Unused.push_back(Inc.atLine(4));

Expand All @@ -429,7 +433,7 @@ R"cpp(#include "d.h"
)cpp");

Results = {};
Results.Missing.push_back("\"d.h\"");
Results.Missing.emplace_back("\"d.h\"", Header(""));
Code = R"cpp(#include "a.h")cpp";
EXPECT_EQ(fixIncludes(Results, "d.cc", Code, format::getLLVMStyle()),
R"cpp(#include "d.h"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class FindHeadersTest : public testing::Test {
llvm::SmallVector<Hinted<Header>> findHeaders(llvm::StringRef FileName) {
return include_cleaner::findHeaders(
AST->sourceManager().translateFileLineCol(
AST->fileManager().getFile(FileName).get(),
*AST->fileManager().getOptionalFileRef(FileName),
/*Line=*/1, /*Col=*/1),
AST->sourceManager(), &PI);
}
Expand Down
85 changes: 42 additions & 43 deletions clang-tools-extra/include-cleaner/unittests/RecordTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ TEST_F(RecordPPTest, CapturesMacroRefs) {
const auto &SM = AST.sourceManager();

SourceLocation Def = SM.getComposedLoc(
SM.translateFile(AST.fileManager().getFile("header.h").get()),
SM.translateFile(*AST.fileManager().getOptionalFileRef("header.h")),
Header.point("def"));
ASSERT_THAT(Recorded.MacroReferences, Not(IsEmpty()));
Symbol OrigX = Recorded.MacroReferences.front().Target;
Expand Down Expand Up @@ -368,29 +368,29 @@ TEST_F(PragmaIncludeTest, IWYUKeep) {
TestAST Processed = build();
auto &FM = Processed.fileManager();

EXPECT_FALSE(PI.shouldKeep(FM.getFile("normal.h").get()));
EXPECT_FALSE(PI.shouldKeep(FM.getFile("std/vector").get()));
EXPECT_FALSE(PI.shouldKeep(*FM.getOptionalFileRef("normal.h")));
EXPECT_FALSE(PI.shouldKeep(*FM.getOptionalFileRef("std/vector")));

// Keep
EXPECT_TRUE(PI.shouldKeep(FM.getFile("keep1.h").get()));
EXPECT_TRUE(PI.shouldKeep(FM.getFile("keep2.h").get()));
EXPECT_TRUE(PI.shouldKeep(FM.getFile("keep3.h").get()));
EXPECT_TRUE(PI.shouldKeep(FM.getFile("keep4.h").get()));
EXPECT_TRUE(PI.shouldKeep(FM.getFile("keep5.h").get()));
EXPECT_TRUE(PI.shouldKeep(FM.getFile("keep6.h").get()));
EXPECT_TRUE(PI.shouldKeep(FM.getFile("std/map").get()));
EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("keep1.h")));
EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("keep2.h")));
EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("keep3.h")));
EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("keep4.h")));
EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("keep5.h")));
EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("keep6.h")));
EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("std/map")));

// Exports
EXPECT_TRUE(PI.shouldKeep(FM.getFile("export1.h").get()));
EXPECT_TRUE(PI.shouldKeep(FM.getFile("export2.h").get()));
EXPECT_TRUE(PI.shouldKeep(FM.getFile("export3.h").get()));
EXPECT_TRUE(PI.shouldKeep(FM.getFile("std/set").get()));
EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("export1.h")));
EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("export2.h")));
EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("export3.h")));
EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("std/set")));
}

TEST_F(PragmaIncludeTest, AssociatedHeader) {
createEmptyFiles({"foo/main.h", "bar/main.h", "bar/other.h", "std/vector"});
auto IsKeep = [&](llvm::StringRef Name, TestAST &AST) {
return PI.shouldKeep(AST.fileManager().getFile(Name).get());
return PI.shouldKeep(*AST.fileManager().getOptionalFileRef(Name));
};

Inputs.FileName = "main.cc";
Expand Down Expand Up @@ -452,19 +452,19 @@ TEST_F(PragmaIncludeTest, IWYUPrivate) {
// IWYU pragma: private
)cpp";
TestAST Processed = build();
auto PrivateFE = Processed.fileManager().getFile("private.h");
auto PrivateFE = Processed.fileManager().getOptionalFileRef("private.h");
assert(PrivateFE);
EXPECT_TRUE(PI.isPrivate(PrivateFE.get()));
EXPECT_EQ(PI.getPublic(PrivateFE.get()), "\"public2.h\"");
EXPECT_TRUE(PI.isPrivate(*PrivateFE));
EXPECT_EQ(PI.getPublic(*PrivateFE), "\"public2.h\"");

auto PublicFE = Processed.fileManager().getFile("public.h");
auto PublicFE = Processed.fileManager().getOptionalFileRef("public.h");
assert(PublicFE);
EXPECT_EQ(PI.getPublic(PublicFE.get()), ""); // no mapping.
EXPECT_FALSE(PI.isPrivate(PublicFE.get()));
EXPECT_EQ(PI.getPublic(*PublicFE), ""); // no mapping.
EXPECT_FALSE(PI.isPrivate(*PublicFE));

auto Private2FE = Processed.fileManager().getFile("private2.h");
auto Private2FE = Processed.fileManager().getOptionalFileRef("private2.h");
assert(Private2FE);
EXPECT_TRUE(PI.isPrivate(Private2FE.get()));
EXPECT_TRUE(PI.isPrivate(*Private2FE));
}

TEST_F(PragmaIncludeTest, IWYUExport) {
Expand All @@ -486,13 +486,13 @@ TEST_F(PragmaIncludeTest, IWYUExport) {
const auto &SM = Processed.sourceManager();
auto &FM = Processed.fileManager();

EXPECT_THAT(PI.getExporters(FM.getFile("private.h").get(), FM),
EXPECT_THAT(PI.getExporters(*FM.getOptionalFileRef("private.h"), FM),
testing::UnorderedElementsAre(FileNamed("export1.h"),
FileNamed("export3.h")));

EXPECT_TRUE(PI.getExporters(FM.getFile("export1.h").get(), FM).empty());
EXPECT_TRUE(PI.getExporters(FM.getFile("export2.h").get(), FM).empty());
EXPECT_TRUE(PI.getExporters(FM.getFile("export3.h").get(), FM).empty());
EXPECT_TRUE(PI.getExporters(*FM.getOptionalFileRef("export1.h"), FM).empty());
EXPECT_TRUE(PI.getExporters(*FM.getOptionalFileRef("export2.h"), FM).empty());
EXPECT_TRUE(PI.getExporters(*FM.getOptionalFileRef("export3.h"), FM).empty());
EXPECT_TRUE(
PI.getExporters(SM.getFileEntryForID(SM.getMainFileID()), FM).empty());
}
Expand Down Expand Up @@ -546,26 +546,25 @@ TEST_F(PragmaIncludeTest, IWYUExportBlock) {
for (auto &FE : FEs) {
OS << FE.getName() << " ";
}
OS.flush();
return Result;
};
auto Exporters = PI.getExporters(FM.getFile("private1.h").get(), FM);
auto Exporters = PI.getExporters(*FM.getOptionalFileRef("private1.h"), FM);
EXPECT_THAT(Exporters, testing::UnorderedElementsAre(FileNamed("export1.h"),
FileNamed("normal.h")))
<< GetNames(Exporters);

Exporters = PI.getExporters(FM.getFile("private2.h").get(), FM);
Exporters = PI.getExporters(*FM.getOptionalFileRef("private2.h"), FM);
EXPECT_THAT(Exporters, testing::UnorderedElementsAre(FileNamed("export1.h")))
<< GetNames(Exporters);

Exporters = PI.getExporters(FM.getFile("private3.h").get(), FM);
Exporters = PI.getExporters(*FM.getOptionalFileRef("private3.h"), FM);
EXPECT_THAT(Exporters, testing::UnorderedElementsAre(FileNamed("export1.h")))
<< GetNames(Exporters);

Exporters = PI.getExporters(FM.getFile("foo.h").get(), FM);
Exporters = PI.getExporters(*FM.getOptionalFileRef("foo.h"), FM);
EXPECT_TRUE(Exporters.empty()) << GetNames(Exporters);

Exporters = PI.getExporters(FM.getFile("bar.h").get(), FM);
Exporters = PI.getExporters(*FM.getOptionalFileRef("bar.h"), FM);
EXPECT_TRUE(Exporters.empty()) << GetNames(Exporters);
}

Expand All @@ -581,8 +580,8 @@ TEST_F(PragmaIncludeTest, SelfContained) {
Inputs.ExtraFiles["unguarded.h"] = "";
TestAST Processed = build();
auto &FM = Processed.fileManager();
EXPECT_TRUE(PI.isSelfContained(FM.getFile("guarded.h").get()));
EXPECT_FALSE(PI.isSelfContained(FM.getFile("unguarded.h").get()));
EXPECT_TRUE(PI.isSelfContained(*FM.getOptionalFileRef("guarded.h")));
EXPECT_FALSE(PI.isSelfContained(*FM.getOptionalFileRef("unguarded.h")));
}

TEST_F(PragmaIncludeTest, AlwaysKeep) {
Expand All @@ -597,8 +596,8 @@ TEST_F(PragmaIncludeTest, AlwaysKeep) {
Inputs.ExtraFiles["usual.h"] = "#pragma once";
TestAST Processed = build();
auto &FM = Processed.fileManager();
EXPECT_TRUE(PI.shouldKeep(FM.getFile("always_keep.h").get()));
EXPECT_FALSE(PI.shouldKeep(FM.getFile("usual.h").get()));
EXPECT_TRUE(PI.shouldKeep(*FM.getOptionalFileRef("always_keep.h")));
EXPECT_FALSE(PI.shouldKeep(*FM.getOptionalFileRef("usual.h")));
}

TEST_F(PragmaIncludeTest, ExportInUnnamedBuffer) {
Expand Down Expand Up @@ -654,13 +653,13 @@ TEST_F(PragmaIncludeTest, OutlivesFMAndSM) {
// Now this build gives us a new File&Source Manager.
TestAST Processed = build(/*ResetPragmaIncludes=*/false);
auto &FM = Processed.fileManager();
auto PrivateFE = FM.getFile("private.h");
auto PrivateFE = FM.getOptionalFileRef("private.h");
assert(PrivateFE);
EXPECT_EQ(PI.getPublic(PrivateFE.get()), "\"public.h\"");
EXPECT_EQ(PI.getPublic(*PrivateFE), "\"public.h\"");

auto Private2FE = FM.getFile("private2.h");
auto Private2FE = FM.getOptionalFileRef("private2.h");
assert(Private2FE);
EXPECT_THAT(PI.getExporters(Private2FE.get(), FM),
EXPECT_THAT(PI.getExporters(*Private2FE, FM),
testing::ElementsAre(llvm::cantFail(FM.getFileRef("public.h"))));
}

Expand All @@ -677,8 +676,8 @@ TEST_F(PragmaIncludeTest, CanRecordManyTimes) {

TestAST Processed = build();
auto &FM = Processed.fileManager();
auto PrivateFE = FM.getFile("private.h");
llvm::StringRef Public = PI.getPublic(PrivateFE.get());
auto PrivateFE = FM.getOptionalFileRef("private.h");
llvm::StringRef Public = PI.getPublic(*PrivateFE);
EXPECT_EQ(Public, "\"public.h\"");

// This build populates same PI during build, but this time we don't have
Expand Down
1 change: 0 additions & 1 deletion clang-tools-extra/modularize/Modularize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,6 @@ class CollectEntitiesVisitor
std::string Name;
llvm::raw_string_ostream OS(Name);
ND->printQualifiedName(OS);
OS.flush();
if (Name.empty())
return true;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_INPUTS_BDE_TYPES_NULLABLEVALUE_H_
#define LLVM_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_INPUTS_BDE_TYPES_NULLABLEVALUE_H_

#include "bsl_optional.h"

/// Mock of `bdlb::NullableValue`.
namespace BloombergLP::bdlb {

template <typename T>
class NullableValue : public bsl::optional<T> {
public:
constexpr NullableValue() noexcept;

constexpr NullableValue(bsl::nullopt_t) noexcept;

NullableValue(const NullableValue &) = default;

NullableValue(NullableValue &&) = default;

const T &value() const &;
T &value() &;

// 'operator bool' is inherited from bsl::optional

constexpr bool isNull() const noexcept;

template <typename U>
constexpr T valueOr(U &&v) const &;

// 'reset' is inherited from bsl::optional

template <typename U> NullableValue &operator=(const U &u);
};


} // namespace BloombergLP::bdlb

#endif // LLVM_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_INPUTS_BDE_TYPES_NULLABLEVALUE_H_
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_INPUTS_BDE_TYPES_OPTIONAL_H_
#define LLVM_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_INPUTS_BDE_TYPES_OPTIONAL_H_

/// Mock of `bsl::optional`.
namespace bsl {

// clang-format off
template <typename T> struct remove_reference { using type = T; };
template <typename T> struct remove_reference<T&> { using type = T; };
template <typename T> struct remove_reference<T&&> { using type = T; };
// clang-format on

template <typename T>
using remove_reference_t = typename remove_reference<T>::type;

template <typename T>
constexpr T &&forward(remove_reference_t<T> &t) noexcept;

template <typename T>
constexpr T &&forward(remove_reference_t<T> &&t) noexcept;

template <typename T>
constexpr remove_reference_t<T> &&move(T &&x);

struct nullopt_t {
constexpr explicit nullopt_t() {}
};

constexpr nullopt_t nullopt;

template <typename T>
class optional {
public:
constexpr optional() noexcept;

constexpr optional(nullopt_t) noexcept;

optional(const optional &) = default;

optional(optional &&) = default;

const T &operator*() const &;
T &operator*() &;
const T &&operator*() const &&;
T &&operator*() &&;

const T *operator->() const;
T *operator->();

const T &value() const &;
T &value() &;
const T &&value() const &&;
T &&value() &&;

constexpr explicit operator bool() const noexcept;
constexpr bool has_value() const noexcept;

template <typename U>
constexpr T value_or(U &&v) const &;
template <typename U>
T value_or(U &&v) &&;

template <typename... Args>
T &emplace(Args &&...args);

void reset() noexcept;

void swap(optional &rhs) noexcept;

template <typename U> optional &operator=(const U &u);
};

} // namespace bsl

#endif // LLVM_CLANG_TOOLS_EXTRA_TEST_CLANG_TIDY_CHECKERS_INPUTS_BDE_TYPES_OPTIONAL_H_
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ void warningLessThanZero() {
if (pthread_yield() < 0) {}
// CHECK-MESSAGES: :[[@LINE-1]]:23: warning:
// CHECK-FIXES: pthread_yield() > 0
if (0 > pthread_yield() ) {}
// CHECK-MESSAGES: :[[@LINE-1]]:9: warning:
// CHECK-FIXES: 0 < pthread_yield()

}

Expand All @@ -90,7 +93,8 @@ void warningAlwaysTrue() {
// CHECK-MESSAGES: :[[@LINE-1]]:31: warning:
if (pthread_yield() >= 0) {}
// CHECK-MESSAGES: :[[@LINE-1]]:23: warning:

if (0 <= pthread_yield()) {}
// CHECK-MESSAGES: :[[@LINE-1]]:9: warning:
}

void warningEqualsNegative() {
Expand Down Expand Up @@ -120,7 +124,14 @@ void warningEqualsNegative() {
// CHECK-MESSAGES: :[[@LINE-1]]:46: warning:
if (pthread_create(NULL, NULL, NULL, NULL) < -1) {}
// CHECK-MESSAGES: :[[@LINE-1]]:46: warning:

if (-1 == pthread_create(NULL, NULL, NULL, NULL)) {}
// CHECK-MESSAGES: :[[@LINE-1]]:10: warning:
if (-1 != pthread_create(NULL, NULL, NULL, NULL)) {}
// CHECK-MESSAGES: :[[@LINE-1]]:10: warning:
if (-1 >= pthread_create(NULL, NULL, NULL, NULL)) {}
// CHECK-MESSAGES: :[[@LINE-1]]:10: warning:
if (-1 > pthread_create(NULL, NULL, NULL, NULL)) {}
// CHECK-MESSAGES: :[[@LINE-1]]:10: warning:
}

void WarningWithMacro() {
Expand Down Expand Up @@ -162,6 +173,16 @@ void noWarning() {
if (posix_openpt(0) < -1) {}
if (posix_fadvise(0, 0, 0, 0) <= 0) {}
if (posix_fadvise(0, 0, 0, 0) == 1) {}
if (0 > posix_openpt(0)) {}
if (0 >= posix_openpt(0)) {}
if (-1 == posix_openpt(0)) {}
if (-1 != posix_openpt(0)) {}
if (-1 >= posix_openpt(0)) {}
if (-1 > posix_openpt(0)) {}
if (posix_fadvise(0, 0, 0, 0) <= 0) {}
if (posix_fadvise(0, 0, 0, 0) == 1) {}
if (0 >= posix_fadvise(0, 0, 0, 0)) {}
if (1 == posix_fadvise(0, 0, 0, 0)) {}
}

namespace i {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// RUN: %check_clang_tidy %s bugprone-tagged-union-member-count %t \
// RUN: -config='{CheckOptions: { \
// RUN: bugprone-tagged-union-member-count.EnableCountingEnumHeuristic: false, \
// RUN: bugprone-tagged-union-member-count.CountingEnumSuffixes: "count", \
// RUN: bugprone-tagged-union-member-count.CountingEnumPrefixes: "last", \
// RUN: }}'

// Warn when the heuristic is disabled and a suffix or a prefix is set explicitly.

// CHECK-MESSAGES: warning: bugprone-tagged-union-member-count: Counting enum heuristic is disabled but CountingEnumPrefixes is set
// CHECK-MESSAGES: warning: bugprone-tagged-union-member-count: Counting enum heuristic is disabled but CountingEnumSuffixes is set
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
// RUN: -config='{CheckOptions: { \
// RUN: bugprone-tagged-union-member-count.StrictMode: true, \
// RUN: bugprone-tagged-union-member-count.EnableCountingEnumHeuristic: false, \
// RUN: }}' --

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has fewer data members (3) than tags (4)
struct IncorrectBecauseHeuristicIsDisabledPrefixCase {
enum {
tags11,
tags22,
tags33,
lasttag,
} Tags;
union {
char A;
short B;
int C;
} Data;
};

struct CorrectBecauseHeuristicIsDisabledPrefixCase { // No warnings expected
enum {
tags1,
tags2,
tags3,
lasttags,
} Tags;
union {
char A;
short B;
int C;
long D;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has fewer data members (3) than tags (4)
struct IncorrectBecauseHeuristicIsDisabledSuffixCase {
enum {
tags11,
tags22,
tags33,
tags_count,
} Tags;
union {
char A;
short B;
int C;
} Data;
};

struct CorrectBecauseHeuristicIsDisabledSuffixCase { // No warnings expected
enum {
tags1,
tags2,
tags3,
tags_count,
} Tags;
union {
char A;
short B;
int C;
long D;
} Data;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
// RUN: -config='{CheckOptions: { \
// RUN: bugprone-tagged-union-member-count.StrictMode: false, \
// RUN: bugprone-tagged-union-member-count.EnableCountingEnumHeuristic: true, \
// RUN: bugprone-tagged-union-member-count.CountingEnumSuffixes: "count", \
// RUN: bugprone-tagged-union-member-count.CountingEnumPrefixes: "last", \
// RUN: }}' --

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (3) than tags (2)
struct IncorrectBecauseHeuristicIsEnabledPrefixCase {
enum {
tags1,
tags2,
lasttag,
} Tags;
union {
char A;
short B;
int C;
} Data;
};

struct CorrectBecauseHeuristicIsEnabledPrefixCase { // No warnings expected
enum {
tags1,
tags2,
tags3,
lasttag,
} Tags;
union {
int A;
int B;
int C;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (3) than tags (2)
struct IncorrectBecauseHeuristicIsEnabledSuffixCase {
enum {
tags1,
tags2,
tags_count,
} Tags;
union {
char A;
short B;
int C;
} Data;
};

struct CorrectBecauseHeuristicIsEnabledSuffixCase { // No warnings expected
enum {
tags1,
tags2,
tags3,
tags_count,
} Tags;
union {
int A;
int B;
int C;
} Data;
};

union Union4 {
short *Shorts;
double *Doubles;
int *Ints;
float *Floats;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct CountingEnumCaseInsensitivityTest1 {
enum {
node_type_loop,
node_type_branch,
node_type_function,
node_type_count,
} Kind;
union Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct CountingEnumCaseInsensitivityTest2 {
enum {
NODE_TYPE_LOOP,
NODE_TYPE_BRANCH,
NODE_TYPE_FUNCTION,
NODE_TYPE_COUNT,
} Kind;
union Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TagWhereCountingEnumIsAliased {
enum {
tag_alias_counter1 = 1,
tag_alias_counter2 = 2,
tag_alias_counter3 = 3,
tag_alias_other_count = 3,
} Kind;
union {
char C;
short S;
int I;
long L;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (2)
struct TagWithCountingEnumButOtherValueIsAliased {
enum {
tag_alias_other1 = 1,
tag_alias_other2 = 1,
tag_alias_other3 = 3,
tag_alias_other_count = 2,
} Kind;
union {
char C;
short S;
int I;
long L;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TagWhereCounterIsTheSmallest {
enum {
tag_large1 = 1000,
tag_large2 = 1001,
tag_large3 = 1002,
tag_large_count = 3,
} Kind;
union {
char C;
short S;
int I;
long L;
} Data;
};

// No warnings expected, only the last enum constant can be a counting enum constant
struct TagWhereCounterLikeNameIsNotLast {
enum {
kind_count,
kind2,
last_kind1,
kind3,
} Kind;
union {
char C;
short S;
int I;
long L;
} Data;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
// RUN: -config='{CheckOptions: { \
// RUN: bugprone-tagged-union-member-count.StrictMode: false, \
// RUN: bugprone-tagged-union-member-count.EnableCountingEnumHeuristic: true, \
// RUN: bugprone-tagged-union-member-count.CountingEnumSuffixes: "count", \
// RUN: bugprone-tagged-union-member-count.CountingEnumPrefixes: "last", \
// RUN: }}' --

union Union3 {
short *Shorts;
int *Ints;
float *Floats;
};

union Union4 {
short *Shorts;
double *Doubles;
int *Ints;
float *Floats;
};

// The heuristic only considers the last enum constant
// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionPrefixAndSuffixMatch {
enum {
tags1,
tags2,
tagscount,
lasttags
} Kind;
Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (3) than tags (2)
struct TaggedUnionOnlyPrefixMatch {
enum {
prefixtag1,
prefixtag2,
lastprefixtag
} Kind;
Union3 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (3) than tags (2)
struct TaggedUnionOnlySuffixMatch {
enum {
suffixtag1,
suffixtag2,
suffixtagcount
} Kind;
Union3 Data;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
// RUN: -config='{CheckOptions: { \
// RUN: bugprone-tagged-union-member-count.StrictMode: false, \
// RUN: bugprone-tagged-union-member-count.EnableCountingEnumHeuristic: true, \
// RUN: bugprone-tagged-union-member-count.CountingEnumPrefixes: "maxsize;last", \
// RUN: }}' --

union Union4 {
short *Shorts;
double *Doubles;
int *Ints;
float *Floats;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionWithMaxsizeAsCounterPrefix {
enum {
twc1,
twc2,
twc3,
maxsizetwc,
} Kind;
Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionWithLastAsCounterPrefix {
enum {
twc11,
twc22,
twc33,
lasttwc,
} Kind;
Union4 Data;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
// RUN: -config='{CheckOptions: { \
// RUN: bugprone-tagged-union-member-count.StrictMode: false, \
// RUN: bugprone-tagged-union-member-count.EnableCountingEnumHeuristic: true, \
// RUN: bugprone-tagged-union-member-count.CountingEnumSuffixes: "count;size", \
// RUN: }}' --

typedef union Union4 {
short *Shorts;
double *Doubles;
int *Ints;
float *Floats;
} union4;

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionWithCounterCountSuffix {
enum {
twc1,
twc2,
twc3,
twc_count,
} Kind;
union Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionWithCounterSizeSuffix {
enum {
twc11,
twc22,
twc33,
twc_size,
} Kind;
union Union4 Data;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
// RUN: -config='{CheckOptions: { \
// RUN: bugprone-tagged-union-member-count.StrictMode: false, \
// RUN: }}' --

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (2) than tags (1)
struct Incorrect {
enum {
tags1,
} Tags;
union {
char A;
short B;
} Data;
};

struct CorrectBecauseStrictModeIsDisabled { // No warnings expected
enum {
tags1,
tags2,
tags3,
} Tags;
union {
char A;
short B;
} Data;
};
Loading