CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
356 lines
14 KiB
C++
356 lines
14 KiB
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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "ConstCorrectnessCheck.h"
|
|
#include "../utils/FixItHintUtils.h"
|
|
#include "../utils/Matchers.h"
|
|
#include "../utils/OptionsUtils.h"
|
|
#include "clang/AST/ASTContext.h"
|
|
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
|
#include "clang/ASTMatchers/ASTMatchers.h"
|
|
#include <cassert>
|
|
|
|
using namespace clang::ast_matchers;
|
|
|
|
namespace clang::tidy::misc {
|
|
|
|
namespace {
|
|
// FIXME: This matcher exists in some other code-review as well.
|
|
// It should probably move to ASTMatchers.
|
|
AST_MATCHER(VarDecl, isLocal) { return Node.isLocalVarDecl(); }
|
|
// FIXME: The matcher 'hasName(Name)' asserts that its argument 'Name' is
|
|
// nonempty. Perhaps remove that assertion and replace 'isUnnamed()' with
|
|
// 'hasName("")'.
|
|
AST_MATCHER(VarDecl, isUnnamed) {
|
|
return Node.getDeclName().isIdentifier() && Node.getName().empty();
|
|
}
|
|
AST_MATCHER_P(DeclStmt, containsAnyDeclaration,
|
|
ast_matchers::internal::Matcher<Decl>, InnerMatcher) {
|
|
return ast_matchers::internal::matchesFirstInPointerRange(
|
|
InnerMatcher, Node.decl_begin(), Node.decl_end(), Finder,
|
|
Builder) != Node.decl_end();
|
|
}
|
|
AST_MATCHER(ReferenceType, isSpelledAsLValue) {
|
|
return Node.isSpelledAsLValue();
|
|
}
|
|
AST_MATCHER(Type, isDependentType) { return Node.isDependentType(); }
|
|
|
|
AST_MATCHER(FunctionDecl, isTemplate) {
|
|
return Node.getDescribedFunctionTemplate() != nullptr;
|
|
}
|
|
|
|
AST_MATCHER(FunctionDecl, isFunctionTemplateSpecialization) {
|
|
return Node.isFunctionTemplateSpecialization();
|
|
}
|
|
} // namespace
|
|
|
|
ConstCorrectnessCheck::ConstCorrectnessCheck(StringRef Name,
|
|
ClangTidyContext *Context)
|
|
: ClangTidyCheck(Name, Context),
|
|
AnalyzePointers(Options.get("AnalyzePointers", true)),
|
|
AnalyzeReferences(Options.get("AnalyzeReferences", true)),
|
|
AnalyzeValues(Options.get("AnalyzeValues", true)),
|
|
AnalyzeParameters(Options.get("AnalyzeParameters", true)),
|
|
|
|
WarnPointersAsPointers(Options.get("WarnPointersAsPointers", true)),
|
|
WarnPointersAsValues(Options.get("WarnPointersAsValues", false)),
|
|
|
|
TransformPointersAsPointers(
|
|
Options.get("TransformPointersAsPointers", true)),
|
|
TransformPointersAsValues(
|
|
Options.get("TransformPointersAsValues", false)),
|
|
TransformReferences(Options.get("TransformReferences", true)),
|
|
TransformValues(Options.get("TransformValues", true)),
|
|
|
|
AllowedTypes(
|
|
utils::options::parseStringList(Options.get("AllowedTypes", ""))) {
|
|
if (AnalyzeValues == false && AnalyzeReferences == false &&
|
|
AnalyzePointers == false)
|
|
this->configurationDiag(
|
|
"The check 'misc-const-correctness' will not "
|
|
"perform any analysis because 'AnalyzeValues', "
|
|
"'AnalyzeReferences' and 'AnalyzePointers' are false.");
|
|
}
|
|
|
|
void ConstCorrectnessCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
|
|
Options.store(Opts, "AnalyzePointers", AnalyzePointers);
|
|
Options.store(Opts, "AnalyzeReferences", AnalyzeReferences);
|
|
Options.store(Opts, "AnalyzeValues", AnalyzeValues);
|
|
Options.store(Opts, "AnalyzeParameters", AnalyzeParameters);
|
|
|
|
Options.store(Opts, "WarnPointersAsPointers", WarnPointersAsPointers);
|
|
Options.store(Opts, "WarnPointersAsValues", WarnPointersAsValues);
|
|
|
|
Options.store(Opts, "TransformPointersAsPointers",
|
|
TransformPointersAsPointers);
|
|
Options.store(Opts, "TransformPointersAsValues", TransformPointersAsValues);
|
|
Options.store(Opts, "TransformReferences", TransformReferences);
|
|
Options.store(Opts, "TransformValues", TransformValues);
|
|
|
|
Options.store(Opts, "AllowedTypes",
|
|
utils::options::serializeStringList(AllowedTypes));
|
|
}
|
|
|
|
void ConstCorrectnessCheck::registerMatchers(MatchFinder *Finder) {
|
|
const auto ConstType =
|
|
hasType(qualType(isConstQualified(),
|
|
// pointee check will check the constness of pointer
|
|
unless(pointerType())));
|
|
|
|
const auto ConstReference = hasType(references(isConstQualified()));
|
|
const auto RValueReference = hasType(
|
|
referenceType(anyOf(rValueReferenceType(), unless(isSpelledAsLValue()))));
|
|
|
|
const auto TemplateType = anyOf(
|
|
hasType(hasCanonicalType(templateTypeParmType())),
|
|
hasType(substTemplateTypeParmType()), hasType(isDependentType()),
|
|
// References to template types, their substitutions or typedefs to
|
|
// template types need to be considered as well.
|
|
hasType(referenceType(pointee(hasCanonicalType(templateTypeParmType())))),
|
|
hasType(referenceType(pointee(substTemplateTypeParmType()))));
|
|
|
|
auto AllowedTypeDecl = namedDecl(anyOf(
|
|
matchers::matchesAnyListedRegexName(AllowedTypes), usingShadowDecl()));
|
|
|
|
const auto AllowedType = hasType(qualType(
|
|
anyOf(hasDeclaration(AllowedTypeDecl), references(AllowedTypeDecl),
|
|
pointerType(pointee(hasDeclaration(AllowedTypeDecl))))));
|
|
|
|
const auto AutoTemplateType = varDecl(
|
|
anyOf(hasType(autoType()), hasType(referenceType(pointee(autoType()))),
|
|
hasType(pointerType(pointee(autoType())))));
|
|
|
|
const auto FunctionPointerRef =
|
|
hasType(hasCanonicalType(referenceType(pointee(functionType()))));
|
|
|
|
const auto CommonExcludeTypes =
|
|
anyOf(ConstType, ConstReference, RValueReference, TemplateType,
|
|
FunctionPointerRef, hasType(cxxRecordDecl(isLambda())),
|
|
AutoTemplateType, isImplicit(), AllowedType);
|
|
|
|
// Match local variables which could be 'const' if not modified later.
|
|
// Example: `int i = 10` would match `int i`.
|
|
const auto LocalValDecl =
|
|
varDecl(isLocal(), hasInitializer(unless(isInstantiationDependent())),
|
|
unless(CommonExcludeTypes));
|
|
|
|
// Match the function scope for which the analysis of all local variables
|
|
// shall be run.
|
|
const auto FunctionScope =
|
|
functionDecl(hasBody(stmt(forEachDescendant(
|
|
declStmt(containsAnyDeclaration(
|
|
LocalValDecl.bind("value")),
|
|
unless(has(decompositionDecl())))
|
|
.bind("decl-stmt")))
|
|
.bind("scope")))
|
|
.bind("function-decl");
|
|
|
|
Finder->addMatcher(FunctionScope, this);
|
|
|
|
if (AnalyzeParameters) {
|
|
const auto ParamMatcher =
|
|
parmVarDecl(unless(CommonExcludeTypes), unless(isUnnamed()),
|
|
anyOf(hasType(referenceType()), hasType(pointerType())))
|
|
.bind("value");
|
|
|
|
// Match function parameters which could be 'const' if not modified later.
|
|
// Example: `void foo(int* ptr)` would match `int* ptr`.
|
|
const auto FunctionWithParams =
|
|
functionDecl(
|
|
hasBody(stmt().bind("scope")), has(typeLoc(forEach(ParamMatcher))),
|
|
unless(cxxMethodDecl()), unless(isFunctionTemplateSpecialization()),
|
|
unless(isTemplate()))
|
|
.bind("function-decl");
|
|
|
|
Finder->addMatcher(FunctionWithParams, this);
|
|
}
|
|
}
|
|
|
|
static void addConstFixits(DiagnosticBuilder &Diag, const VarDecl *Variable,
|
|
const FunctionDecl *Function, ASTContext &Context,
|
|
Qualifiers::TQ Qualifier,
|
|
utils::fixit::QualifierTarget Target,
|
|
utils::fixit::QualifierPolicy Policy) {
|
|
// If this is a parameter, also add fixits for corresponding parameters in
|
|
// function declarations
|
|
if (const auto *ParamDecl = dyn_cast<ParmVarDecl>(Variable)) {
|
|
const unsigned ParamIdx = ParamDecl->getFunctionScopeIndex();
|
|
// Skip if all fix-its can not be applied properly due to 'using'/'typedef'
|
|
if (llvm::any_of(
|
|
Function->redecls(), [ParamIdx](const FunctionDecl *Redecl) {
|
|
const QualType Type = Redecl->getParamDecl(ParamIdx)->getType();
|
|
return Type->isTypedefNameType() || Type->getAs<UsingType>();
|
|
}))
|
|
return;
|
|
|
|
for (const FunctionDecl *Redecl : Function->redecls()) {
|
|
Diag << addQualifierToVarDecl(*Redecl->getParamDecl(ParamIdx), Context,
|
|
Qualifier, Target, Policy);
|
|
}
|
|
} else {
|
|
Diag << addQualifierToVarDecl(*Variable, Context, Qualifier, Target,
|
|
Policy);
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
|
|
/// Classify for a variable in what the Const-Check is interested.
|
|
enum class VariableCategory { Value, Reference, Pointer };
|
|
|
|
} // namespace
|
|
|
|
void ConstCorrectnessCheck::check(const MatchFinder::MatchResult &Result) {
|
|
const auto *LocalScope = Result.Nodes.getNodeAs<Stmt>("scope");
|
|
const auto *Variable = Result.Nodes.getNodeAs<VarDecl>("value");
|
|
const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>("function-decl");
|
|
const auto *VarDeclStmt = Result.Nodes.getNodeAs<DeclStmt>("decl-stmt");
|
|
|
|
assert(Variable && LocalScope && Function);
|
|
|
|
// It can not be guaranteed that the variable is declared isolated,
|
|
// therefore a transformation might effect the other variables as well and
|
|
// be incorrect. Parameters don't need this check - they receive values from
|
|
// callers.
|
|
const bool CanBeFixIt = isa<ParmVarDecl>(Variable) ||
|
|
(VarDeclStmt && VarDeclStmt->isSingleDecl());
|
|
|
|
/// If the variable was declared in a template it might be analyzed multiple
|
|
/// times. Only one of those instantiations shall emit a warning. NOTE: This
|
|
/// shall only deduplicate warnings for variables that are not instantiation
|
|
/// dependent. Variables like 'int x = 42;' in a template that can become
|
|
/// const emit multiple warnings otherwise.
|
|
bool IsNormalVariableInTemplate = Function->isTemplateInstantiation();
|
|
if (IsNormalVariableInTemplate &&
|
|
TemplateDiagnosticsCache.contains(Variable->getBeginLoc()))
|
|
return;
|
|
|
|
VariableCategory VC = VariableCategory::Value;
|
|
const QualType VT = Variable->getType();
|
|
if (VT->isReferenceType()) {
|
|
VC = VariableCategory::Reference;
|
|
} else if (VT->isPointerType()) {
|
|
VC = VariableCategory::Pointer;
|
|
} else if (const auto *ArrayT = dyn_cast<ArrayType>(VT)) {
|
|
if (ArrayT->getElementType()->isPointerType())
|
|
VC = VariableCategory::Pointer;
|
|
}
|
|
|
|
auto CheckValue = [&]() {
|
|
// Offload const-analysis to utility function.
|
|
if (isMutated(Variable, LocalScope, Function, Result.Context))
|
|
return;
|
|
|
|
auto Diag = diag(Variable->getBeginLoc(),
|
|
"variable %0 of type %1 can be declared 'const'")
|
|
<< Variable << VT;
|
|
if (IsNormalVariableInTemplate)
|
|
TemplateDiagnosticsCache.insert(Variable->getBeginLoc());
|
|
if (!CanBeFixIt)
|
|
return;
|
|
using namespace utils::fixit;
|
|
|
|
if (VC == VariableCategory::Value && TransformValues) {
|
|
addConstFixits(Diag, Variable, Function, *Result.Context,
|
|
Qualifiers::Const, QualifierTarget::Value,
|
|
QualifierPolicy::Right);
|
|
// FIXME: Add '{}' for default initialization if no user-defined default
|
|
// constructor exists and there is no initializer.
|
|
return;
|
|
}
|
|
|
|
if (VC == VariableCategory::Reference && TransformReferences) {
|
|
addConstFixits(Diag, Variable, Function, *Result.Context,
|
|
Qualifiers::Const, QualifierTarget::Value,
|
|
QualifierPolicy::Right);
|
|
return;
|
|
}
|
|
|
|
if (VC == VariableCategory::Pointer && TransformPointersAsValues) {
|
|
addConstFixits(Diag, Variable, Function, *Result.Context,
|
|
Qualifiers::Const, QualifierTarget::Value,
|
|
QualifierPolicy::Right);
|
|
return;
|
|
}
|
|
};
|
|
|
|
auto CheckPointee = [&]() {
|
|
assert(VC == VariableCategory::Pointer);
|
|
registerScope(LocalScope, Result.Context);
|
|
if (ScopesCache[LocalScope]->isPointeeMutated(Variable))
|
|
return;
|
|
auto Diag =
|
|
diag(Variable->getBeginLoc(),
|
|
"pointee of variable %0 of type %1 can be declared 'const'")
|
|
<< Variable << VT;
|
|
if (IsNormalVariableInTemplate)
|
|
TemplateDiagnosticsCache.insert(Variable->getBeginLoc());
|
|
if (!CanBeFixIt)
|
|
return;
|
|
using namespace utils::fixit;
|
|
if (TransformPointersAsPointers) {
|
|
addConstFixits(Diag, Variable, Function, *Result.Context,
|
|
Qualifiers::Const, QualifierTarget::Pointee,
|
|
QualifierPolicy::Right);
|
|
}
|
|
};
|
|
|
|
// Each variable can only be in one category: Value, Pointer, Reference.
|
|
// Analysis can be controlled for every category.
|
|
if (VC == VariableCategory::Value && AnalyzeValues) {
|
|
CheckValue();
|
|
return;
|
|
}
|
|
if (VC == VariableCategory::Reference && AnalyzeReferences) {
|
|
if (VT->getPointeeType()->isPointerType() && !WarnPointersAsValues)
|
|
return;
|
|
CheckValue();
|
|
return;
|
|
}
|
|
if (VC == VariableCategory::Pointer && AnalyzePointers) {
|
|
if (WarnPointersAsValues && !VT.isConstQualified())
|
|
CheckValue();
|
|
if (WarnPointersAsPointers) {
|
|
if (const auto *PT = dyn_cast<PointerType>(VT)) {
|
|
if (!PT->getPointeeType().isConstQualified() &&
|
|
!PT->getPointeeType()->isFunctionType())
|
|
CheckPointee();
|
|
}
|
|
if (const auto *AT = dyn_cast<ArrayType>(VT)) {
|
|
assert(AT->getElementType()->isPointerType());
|
|
if (!AT->getElementType()->getPointeeType().isConstQualified())
|
|
CheckPointee();
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
void ConstCorrectnessCheck::registerScope(const Stmt *LocalScope,
|
|
ASTContext *Context) {
|
|
auto &Analyzer = ScopesCache[LocalScope];
|
|
if (!Analyzer)
|
|
Analyzer = std::make_unique<ExprMutationAnalyzer>(*LocalScope, *Context);
|
|
}
|
|
|
|
bool ConstCorrectnessCheck::isMutated(const VarDecl *Variable,
|
|
const Stmt *Scope,
|
|
const FunctionDecl *Func,
|
|
ASTContext *Context) {
|
|
if (const auto *Param = dyn_cast<ParmVarDecl>(Variable)) {
|
|
return FunctionParmMutationAnalyzer::getFunctionParmMutationAnalyzer(
|
|
*Func, *Context, ParamMutationAnalyzerMemoized)
|
|
->isMutated(Param);
|
|
}
|
|
|
|
registerScope(Scope, Context);
|
|
return ScopesCache[Scope]->isMutated(Variable);
|
|
}
|
|
|
|
} // namespace clang::tidy::misc
|