[clang-tools-extra] [llvm] [clang-tidy] Add support for determining constness of more expressions. (PR #82617)
Clement Courbet via cfe-commits
cfe-commits at lists.llvm.org
Mon Feb 26 00:42:48 PST 2024
https://github.com/legrosbuffle updated https://github.com/llvm/llvm-project/pull/82617
>From 9b93c8bf0614e64352de8e210adf6ff316c0133e Mon Sep 17 00:00:00 2001
From: Clement Courbet <courbet at google.com>
Date: Mon, 19 Feb 2024 14:58:27 +0000
Subject: [PATCH 1/5] [llvm-exegesis][NFC] Refactor all `ValidationEvent` info
in a single table.
All data is derived from a single table rather than being spread out
over an enum, a table and the main entry point.
---
llvm/include/llvm/Target/TargetPfmCounters.td | 1 +
.../llvm-exegesis/lib/BenchmarkResult.cpp | 49 +--------------
.../tools/llvm-exegesis/lib/BenchmarkResult.h | 15 +----
llvm/tools/llvm-exegesis/lib/CMakeLists.txt | 1 +
.../lib/LatencyBenchmarkRunner.cpp | 4 +-
llvm/tools/llvm-exegesis/lib/Target.h | 1 +
.../llvm-exegesis/lib/ValidationEvent.cpp | 53 ++++++++++++++++
.../tools/llvm-exegesis/lib/ValidationEvent.h | 60 +++++++++++++++++++
llvm/tools/llvm-exegesis/llvm-exegesis.cpp | 20 +------
9 files changed, 124 insertions(+), 80 deletions(-)
create mode 100644 llvm/tools/llvm-exegesis/lib/ValidationEvent.cpp
create mode 100644 llvm/tools/llvm-exegesis/lib/ValidationEvent.h
diff --git a/llvm/include/llvm/Target/TargetPfmCounters.td b/llvm/include/llvm/Target/TargetPfmCounters.td
index 8c4d5f50c63a24..cfe432a992b71f 100644
--- a/llvm/include/llvm/Target/TargetPfmCounters.td
+++ b/llvm/include/llvm/Target/TargetPfmCounters.td
@@ -35,6 +35,7 @@ class ValidationEvent <int event_number> {
int EventNumber = event_number;
}
+// TableGen names for events defined in `llvm::exegesis::ValidationEvent`.
def InstructionRetired : ValidationEvent<0>;
def L1DCacheLoadMiss : ValidationEvent<1>;
def L1DCacheStoreMiss : ValidationEvent<2>;
diff --git a/llvm/tools/llvm-exegesis/lib/BenchmarkResult.cpp b/llvm/tools/llvm-exegesis/lib/BenchmarkResult.cpp
index 189add6464173f..f84ebd2a4e68ef 100644
--- a/llvm/tools/llvm-exegesis/lib/BenchmarkResult.cpp
+++ b/llvm/tools/llvm-exegesis/lib/BenchmarkResult.cpp
@@ -9,6 +9,7 @@
#include "BenchmarkResult.h"
#include "BenchmarkRunner.h"
#include "Error.h"
+#include "ValidationEvent.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/StringRef.h"
@@ -198,7 +199,7 @@ struct CustomMappingTraits<std::map<exegesis::ValidationEvent, int64_t>> {
static void inputOne(IO &Io, StringRef KeyStr,
std::map<exegesis::ValidationEvent, int64_t> &VI) {
Expected<exegesis::ValidationEvent> Key =
- exegesis::stringToValidationEvent(KeyStr);
+ exegesis::getValidationEventByName(KeyStr);
if (!Key) {
Io.setError("Key is not a valid validation event");
return;
@@ -208,7 +209,7 @@ struct CustomMappingTraits<std::map<exegesis::ValidationEvent, int64_t>> {
static void output(IO &Io, std::map<exegesis::ValidationEvent, int64_t> &VI) {
for (auto &IndividualVI : VI) {
- Io.mapRequired(exegesis::validationEventToString(IndividualVI.first),
+ Io.mapRequired(exegesis::getValidationEventName(IndividualVI.first),
IndividualVI.second);
}
}
@@ -441,49 +442,5 @@ bool operator==(const BenchmarkMeasure &A, const BenchmarkMeasure &B) {
std::tie(B.Key, B.PerInstructionValue, B.PerSnippetValue);
}
-const char *validationEventToString(ValidationEvent VE) {
- switch (VE) {
- case exegesis::ValidationEvent::InstructionRetired:
- return "instructions-retired";
- case exegesis::ValidationEvent::L1DCacheLoadMiss:
- return "l1d-cache-load-misses";
- case exegesis::ValidationEvent::L1DCacheStoreMiss:
- return "l1d-cache-store-misses";
- case exegesis::ValidationEvent::L1ICacheLoadMiss:
- return "l1i-cache-load-misses";
- case exegesis::ValidationEvent::DataTLBLoadMiss:
- return "data-tlb-load-misses";
- case exegesis::ValidationEvent::DataTLBStoreMiss:
- return "data-tlb-store-misses";
- case exegesis::ValidationEvent::InstructionTLBLoadMiss:
- return "instruction-tlb-load-misses";
- case exegesis::ValidationEvent::BranchPredictionMiss:
- return "branch-prediction-misses";
- }
- llvm_unreachable("Unhandled exegesis::ValidationEvent enum");
-}
-
-Expected<ValidationEvent> stringToValidationEvent(StringRef Input) {
- if (Input == "instructions-retired")
- return exegesis::ValidationEvent::InstructionRetired;
- else if (Input == "l1d-cache-load-misses")
- return exegesis::ValidationEvent::L1DCacheLoadMiss;
- else if (Input == "l1d-cache-store-misses")
- return exegesis::ValidationEvent::L1DCacheStoreMiss;
- else if (Input == "l1i-cache-load-misses")
- return exegesis::ValidationEvent::L1ICacheLoadMiss;
- else if (Input == "data-tlb-load-misses")
- return exegesis::ValidationEvent::DataTLBLoadMiss;
- else if (Input == "data-tlb-store-misses")
- return exegesis::ValidationEvent::DataTLBStoreMiss;
- else if (Input == "instruction-tlb-load-misses")
- return exegesis::ValidationEvent::InstructionTLBLoadMiss;
- else if (Input == "branch-prediction-misses")
- return exegesis::ValidationEvent::BranchPredictionMiss;
- else
- return make_error<StringError>("Invalid validation event string",
- errc::invalid_argument);
-}
-
} // namespace exegesis
} // namespace llvm
diff --git a/llvm/tools/llvm-exegesis/lib/BenchmarkResult.h b/llvm/tools/llvm-exegesis/lib/BenchmarkResult.h
index 60115c51bba321..0aecaaeea4b2e7 100644
--- a/llvm/tools/llvm-exegesis/lib/BenchmarkResult.h
+++ b/llvm/tools/llvm-exegesis/lib/BenchmarkResult.h
@@ -17,6 +17,7 @@
#include "LlvmState.h"
#include "RegisterValue.h"
+#include "ValidationEvent.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCInstBuilder.h"
@@ -32,20 +33,6 @@ class Error;
namespace exegesis {
-enum ValidationEvent {
- InstructionRetired,
- L1DCacheLoadMiss,
- L1DCacheStoreMiss,
- L1ICacheLoadMiss,
- DataTLBLoadMiss,
- DataTLBStoreMiss,
- InstructionTLBLoadMiss,
- BranchPredictionMiss
-};
-
-const char *validationEventToString(exegesis::ValidationEvent VE);
-Expected<ValidationEvent> stringToValidationEvent(StringRef Input);
-
enum class BenchmarkPhaseSelectorE {
PrepareSnippet,
PrepareAndAssembleSnippet,
diff --git a/llvm/tools/llvm-exegesis/lib/CMakeLists.txt b/llvm/tools/llvm-exegesis/lib/CMakeLists.txt
index 6ae441d31f07fe..414b49e5e021c2 100644
--- a/llvm/tools/llvm-exegesis/lib/CMakeLists.txt
+++ b/llvm/tools/llvm-exegesis/lib/CMakeLists.txt
@@ -73,6 +73,7 @@ add_llvm_library(LLVMExegesis
SubprocessMemory.cpp
Target.cpp
UopsBenchmarkRunner.cpp
+ ValidationEvent.cpp
LINK_LIBS ${libs}
diff --git a/llvm/tools/llvm-exegesis/lib/LatencyBenchmarkRunner.cpp b/llvm/tools/llvm-exegesis/lib/LatencyBenchmarkRunner.cpp
index a9917a29cce24d..de61fff6432944 100644
--- a/llvm/tools/llvm-exegesis/lib/LatencyBenchmarkRunner.cpp
+++ b/llvm/tools/llvm-exegesis/lib/LatencyBenchmarkRunner.cpp
@@ -107,8 +107,8 @@ Expected<std::vector<BenchmarkMeasure>> LatencyBenchmarkRunner::runMeasurements(
}
for (size_t I = 0; I < ValCounterValues.size(); ++I) {
- LLVM_DEBUG(dbgs() << validationEventToString(ValidationCounters[I])
- << ": " << IterationValCounterValues[I] << "\n");
+ LLVM_DEBUG(dbgs() << getValidationEventName(ValidationCounters[I]) << ": "
+ << IterationValCounterValues[I] << "\n");
ValCounterValues[I] += IterationValCounterValues[I];
}
}
diff --git a/llvm/tools/llvm-exegesis/lib/Target.h b/llvm/tools/llvm-exegesis/lib/Target.h
index 3d6169c9650213..7bbd946b03331f 100644
--- a/llvm/tools/llvm-exegesis/lib/Target.h
+++ b/llvm/tools/llvm-exegesis/lib/Target.h
@@ -22,6 +22,7 @@
#include "LlvmState.h"
#include "PerfHelper.h"
#include "SnippetGenerator.h"
+#include "ValidationEvent.h"
#include "llvm/CodeGen/TargetPassConfig.h"
#include "llvm/IR/CallingConv.h"
#include "llvm/IR/LegacyPassManager.h"
diff --git a/llvm/tools/llvm-exegesis/lib/ValidationEvent.cpp b/llvm/tools/llvm-exegesis/lib/ValidationEvent.cpp
new file mode 100644
index 00000000000000..a212e1c4fdf7c8
--- /dev/null
+++ b/llvm/tools/llvm-exegesis/lib/ValidationEvent.cpp
@@ -0,0 +1,53 @@
+
+#include "ValidationEvent.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Errc.h"
+#include "llvm/Support/Error.h"
+
+namespace llvm {
+namespace exegesis {
+
+namespace {
+
+struct ValidationEventInfo {
+ const char *const Name;
+ const char *const Description;
+};
+
+// Information about validation events, indexed by `ValidationEvent` enum
+// value.
+static constexpr ValidationEventInfo ValidationEventInfos[NumValidationEvents] =
+ {
+ {"instructions-retired", "Count retired instructions"},
+ {"l1d-cache-load-misses", "Count L1D load cache misses"},
+ {"l1d-cache-store-misses", "Count L1D store cache misses"},
+ {"l1i-cache-load-misses", "Count L1I load cache misses"},
+ {"data-tlb-load-misses", "Count DTLB load misses"},
+ {"data-tlb-store-misses", "Count DTLB store misses"},
+ {"instruction-tlb-load-misses", "Count ITLB load misses"},
+ {"branch-prediction-misses", "Branch prediction misses"},
+};
+
+} // namespace
+
+const char *getValidationEventName(ValidationEvent VE) {
+ return ValidationEventInfos[VE].Name;
+}
+const char *getValidationEventDescription(ValidationEvent VE) {
+ return ValidationEventInfos[VE].Description;
+}
+
+Expected<ValidationEvent> getValidationEventByName(StringRef Name) {
+ int VE = 0;
+ for (const ValidationEventInfo &Info : ValidationEventInfos) {
+ if (Name == Info.Name)
+ return static_cast<ValidationEvent>(VE);
+ ++VE;
+ }
+
+ return make_error<StringError>("Invalid validation event string",
+ errc::invalid_argument);
+}
+
+} // namespace exegesis
+} // namespace llvm
diff --git a/llvm/tools/llvm-exegesis/lib/ValidationEvent.h b/llvm/tools/llvm-exegesis/lib/ValidationEvent.h
new file mode 100644
index 00000000000000..8a9f3af57dca97
--- /dev/null
+++ b/llvm/tools/llvm-exegesis/lib/ValidationEvent.h
@@ -0,0 +1,60 @@
+//===-- ValidationEvent.h ---------------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Definitions and utilities for Validation Events.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_TOOLS_LLVM_EXEGESIS_VALIDATIONEVENT_H
+#define LLVM_TOOLS_LLVM_EXEGESIS_VALIDATIONEVENT_H
+
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+namespace llvm {
+
+namespace exegesis {
+
+// The main list of supported validation events. The mapping between validation
+// events and pfm counters is defined in TableDef files for each target.
+enum ValidationEvent {
+ InstructionRetired,
+ L1DCacheLoadMiss,
+ L1DCacheStoreMiss,
+ L1ICacheLoadMiss,
+ DataTLBLoadMiss,
+ DataTLBStoreMiss,
+ InstructionTLBLoadMiss,
+ BranchPredictionMiss,
+ // Number of events.
+ NumValidationEvents,
+};
+
+// Returns the name/description of the given event.
+const char *getValidationEventName(ValidationEvent VE);
+const char *getValidationEventDescription(ValidationEvent VE);
+
+// Returns the ValidationEvent with the given name.
+Expected<ValidationEvent> getValidationEventByName(StringRef Name);
+
+// Command-line options for validation events.
+struct ValidationEventOptions {
+ template <class Opt> void apply(Opt &O) const {
+ for (int I = 0; I < NumValidationEvents; ++I) {
+ const auto VE = static_cast<ValidationEvent>(I);
+ O.getParser().addLiteralOption(getValidationEventName(VE), VE,
+ getValidationEventDescription(VE));
+ }
+ }
+};
+
+} // namespace exegesis
+} // namespace llvm
+
+#endif
diff --git a/llvm/tools/llvm-exegesis/llvm-exegesis.cpp b/llvm/tools/llvm-exegesis/llvm-exegesis.cpp
index ac279029e6b004..66387bdec5a5a6 100644
--- a/llvm/tools/llvm-exegesis/llvm-exegesis.cpp
+++ b/llvm/tools/llvm-exegesis/llvm-exegesis.cpp
@@ -25,6 +25,7 @@
#include "lib/SnippetRepetitor.h"
#include "lib/Target.h"
#include "lib/TargetSelect.h"
+#include "lib/ValidationEvent.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/Twine.h"
#include "llvm/MC/MCInstBuilder.h"
@@ -278,24 +279,7 @@ static cl::list<ValidationEvent> ValidationCounters(
cl::desc(
"The name of a validation counter to run concurrently with the main "
"counter to validate benchmarking assumptions"),
- cl::CommaSeparated, cl::cat(BenchmarkOptions),
- cl::values(
- clEnumValN(ValidationEvent::InstructionRetired, "instructions-retired",
- "Count retired instructions"),
- clEnumValN(ValidationEvent::L1DCacheLoadMiss, "l1d-cache-load-misses",
- "Count L1D load cache misses"),
- clEnumValN(ValidationEvent::L1DCacheStoreMiss, "l1d-cache-store-misses",
- "Count L1D store cache misses"),
- clEnumValN(ValidationEvent::L1ICacheLoadMiss, "l1i-cache-load-misses",
- "Count L1I load cache misses"),
- clEnumValN(ValidationEvent::DataTLBLoadMiss, "data-tlb-load-misses",
- "Count DTLB load misses"),
- clEnumValN(ValidationEvent::DataTLBStoreMiss, "data-tlb-store-misses",
- "Count DTLB store misses"),
- clEnumValN(ValidationEvent::InstructionTLBLoadMiss,
- "instruction-tlb-load-misses", "Count ITLB load misses"),
- clEnumValN(ValidationEvent::BranchPredictionMiss,
- "branch-prediction-misses", "Branch prediction misses")));
+ cl::CommaSeparated, cl::cat(BenchmarkOptions), ValidationEventOptions());
static ExitOnError ExitOnErr("llvm-exegesis error: ");
>From a4e177fd8fe0289742be8a4a90ae010b5fbd5630 Mon Sep 17 00:00:00 2001
From: Clement Courbet <courbet at google.com>
Date: Tue, 20 Feb 2024 08:43:23 +0000
Subject: [PATCH 2/5] `static_assert` that the size of `ValidationEventInfos`
is consistent with that of the enum.
---
.../llvm-exegesis/lib/ValidationEvent.cpp | 23 +++++++++++--------
1 file changed, 13 insertions(+), 10 deletions(-)
diff --git a/llvm/tools/llvm-exegesis/lib/ValidationEvent.cpp b/llvm/tools/llvm-exegesis/lib/ValidationEvent.cpp
index a212e1c4fdf7c8..c965b7ae55e10d 100644
--- a/llvm/tools/llvm-exegesis/lib/ValidationEvent.cpp
+++ b/llvm/tools/llvm-exegesis/lib/ValidationEvent.cpp
@@ -16,18 +16,21 @@ struct ValidationEventInfo {
// Information about validation events, indexed by `ValidationEvent` enum
// value.
-static constexpr ValidationEventInfo ValidationEventInfos[NumValidationEvents] =
- {
- {"instructions-retired", "Count retired instructions"},
- {"l1d-cache-load-misses", "Count L1D load cache misses"},
- {"l1d-cache-store-misses", "Count L1D store cache misses"},
- {"l1i-cache-load-misses", "Count L1I load cache misses"},
- {"data-tlb-load-misses", "Count DTLB load misses"},
- {"data-tlb-store-misses", "Count DTLB store misses"},
- {"instruction-tlb-load-misses", "Count ITLB load misses"},
- {"branch-prediction-misses", "Branch prediction misses"},
+static constexpr ValidationEventInfo ValidationEventInfos[] = {
+ {"instructions-retired", "Count retired instructions"},
+ {"l1d-cache-load-misses", "Count L1D load cache misses"},
+ {"l1d-cache-store-misses", "Count L1D store cache misses"},
+ {"l1i-cache-load-misses", "Count L1I load cache misses"},
+ {"data-tlb-load-misses", "Count DTLB load misses"},
+ {"data-tlb-store-misses", "Count DTLB store misses"},
+ {"instruction-tlb-load-misses", "Count ITLB load misses"},
+ {"branch-prediction-misses", "Branch prediction misses"},
};
+static_assert(sizeof(ValidationEventInfos) ==
+ NumValidationEvents * sizeof(ValidationEventInfo),
+ "please update ValidationEventInfos");
+
} // namespace
const char *getValidationEventName(ValidationEvent VE) {
>From 881946112a2273e76c0daa3ee45bd4dce41e6f68 Mon Sep 17 00:00:00 2001
From: Clement Courbet <courbet at google.com>
Date: Wed, 21 Feb 2024 09:15:22 +0000
Subject: [PATCH 3/5] [clang-tidy] Add support for determining constness of
more expressions.
This uses a more systematic approach for determining whcich `DeclRefExpr`s mutate
the underlying object: Instead of using a few matchers, we walk up the
AST until we find a parent that we can prove cannot change the
underlying object.
This allows us to handle most address taking and dereference, bindings
to value and const& variables, and track constness of pointee
(see changes in DeclRefExprUtilsTest.cpp).
This allows supporting more patterns in `performance-unnecessary-copy-initialization`.
Those two patterns are relatively common:
```
const auto e = (*vector_ptr)[i]
```
and
```
const auto e = vector_ptr->at(i);
```
In our codebase, we have around 25% additional findings from
`performance-unnecessary-copy-initialization` with this change.
I did not see any additional false positives.
---
.../UnnecessaryCopyInitialization.cpp | 27 +-
.../clang-tidy/utils/DeclRefExprUtils.cpp | 212 +++++++++++---
.../clang-tidy/utils/DeclRefExprUtils.h | 33 ++-
clang-tools-extra/docs/ReleaseNotes.rst | 6 +
.../unnecessary-copy-initialization.cpp | 27 +-
.../clang-tidy/DeclRefExprUtilsTest.cpp | 274 +++++++++++-------
6 files changed, 396 insertions(+), 183 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp b/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp
index dfe12c5b6007da..9beb185cba929d 100644
--- a/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp
+++ b/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp
@@ -86,16 +86,22 @@ AST_MATCHER_FUNCTION_P(StatementMatcher, isConstRefReturningMethodCall,
const auto MethodDecl =
cxxMethodDecl(returns(hasCanonicalType(matchers::isReferenceToConst())))
.bind(MethodDeclId);
- const auto ReceiverExpr = declRefExpr(to(varDecl().bind(ObjectArgId)));
+ const auto ReceiverExpr =
+ ignoringParenImpCasts(declRefExpr(to(varDecl().bind(ObjectArgId))));
+ const auto OnExpr = anyOf(
+ // Direct reference to `*this`: `a.f()` or `a->f()`.
+ ReceiverExpr,
+ // Access through dereference, typically used for `operator[]`: `(*a)[3]`.
+ unaryOperator(hasOperatorName("*"), hasUnaryOperand(ReceiverExpr)));
const auto ReceiverType =
hasCanonicalType(recordType(hasDeclaration(namedDecl(
unless(matchers::matchesAnyListedName(ExcludedContainerTypes))))));
- return expr(anyOf(
- cxxMemberCallExpr(callee(MethodDecl), on(ReceiverExpr),
- thisPointerType(ReceiverType)),
- cxxOperatorCallExpr(callee(MethodDecl), hasArgument(0, ReceiverExpr),
- hasArgument(0, hasType(ReceiverType)))));
+ return expr(
+ anyOf(cxxMemberCallExpr(callee(MethodDecl), on(OnExpr),
+ thisPointerType(ReceiverType)),
+ cxxOperatorCallExpr(callee(MethodDecl), hasArgument(0, OnExpr),
+ hasArgument(0, hasType(ReceiverType)))));
}
AST_MATCHER_FUNCTION(StatementMatcher, isConstRefReturningFunctionCall) {
@@ -136,10 +142,11 @@ AST_MATCHER_FUNCTION_P(StatementMatcher, initializerReturnsReferenceToConst,
static bool isInitializingVariableImmutable(
const VarDecl &InitializingVar, const Stmt &BlockStmt, ASTContext &Context,
const std::vector<StringRef> &ExcludedContainerTypes) {
- if (!isOnlyUsedAsConst(InitializingVar, BlockStmt, Context))
+ QualType T = InitializingVar.getType().getCanonicalType();
+ if (!isOnlyUsedAsConst(InitializingVar, BlockStmt, Context,
+ T->isPointerType() ? 1 : 0))
return false;
- QualType T = InitializingVar.getType().getCanonicalType();
// The variable is a value type and we know it is only used as const. Safe
// to reference it and avoid the copy.
if (!isa<ReferenceType, PointerType>(T))
@@ -273,7 +280,9 @@ void UnnecessaryCopyInitialization::check(
VarDeclStmt.isSingleDecl() && !NewVar.getLocation().isMacroID();
const bool IsVarUnused = isVariableUnused(NewVar, BlockStmt, *Result.Context);
const bool IsVarOnlyUsedAsConst =
- isOnlyUsedAsConst(NewVar, BlockStmt, *Result.Context);
+ isOnlyUsedAsConst(NewVar, BlockStmt, *Result.Context,
+ // `NewVar` is always of non-pointer type.
+ 0);
const CheckContext Context{
NewVar, BlockStmt, VarDeclStmt, *Result.Context,
IssueFix, IsVarUnused, IsVarOnlyUsedAsConst};
diff --git a/clang-tools-extra/clang-tidy/utils/DeclRefExprUtils.cpp b/clang-tools-extra/clang-tidy/utils/DeclRefExprUtils.cpp
index 2d73179150e8b8..663691c519b8e9 100644
--- a/clang-tools-extra/clang-tidy/utils/DeclRefExprUtils.cpp
+++ b/clang-tools-extra/clang-tidy/utils/DeclRefExprUtils.cpp
@@ -10,7 +10,9 @@
#include "Matchers.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/DeclCXX.h"
+#include "clang/AST/ExprCXX.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include <cassert>
namespace clang::tidy::utils::decl_ref_expr {
@@ -34,69 +36,185 @@ void extractNodesByIdTo(ArrayRef<BoundNodes> Matches, StringRef ID,
Nodes.insert(Match.getNodeAs<Node>(ID));
}
+// A matcher that matches DeclRefExprs that are used in ways such that the
+// underlying declaration is not modified.
+// If the declaration is of pointer type, `Indirections` specifies the level
+// of indirection of the object whose mutations we are tracking.
+//
+// For example, given:
+// ```
+// int i;
+// int* p;
+// p = &i; // (A)
+// *p = 3; // (B)
+// ```
+//
+// `declRefExpr(to(varDecl(hasName("p"))), doesNotMutateObject(0))` matches
+// (B), but `declRefExpr(to(varDecl(hasName("p"))), doesNotMutateObject(1))`
+// matches (A).
+//
+AST_MATCHER_P(DeclRefExpr, doesNotMutateObject, int, Indirections) {
+ // We walk up the parents of the DeclRefExpr recursively until we end up on a
+ // parent that cannot modify the underlying object. There are a few kinds of
+ // expressions:
+ // - Those that cannot be used to mutate the underlying object. We can stop
+ // recursion there.
+ // - Those that can be used to mutate the underlying object in analyzable
+ // ways (such as taking the address or accessing a subobject). We have to
+ // examine the parents.
+ // - Those that we don't know how to analyze. In that case we stop there and
+ // we assume that they can mutate the underlying expression.
+
+ struct StackEntry {
+ StackEntry(const Expr *E, int Indirections)
+ : E(E), Indirections(Indirections) {}
+ // The expression to analyze.
+ const Expr *E;
+ // The number of pointer indirections of the object being tracked (how
+ // many times an address was taken).
+ int Indirections;
+ };
+
+ llvm::SmallVector<StackEntry, 4> Stack;
+ Stack.emplace_back(&Node, Indirections);
+ auto &Ctx = Finder->getASTContext();
+
+ while (!Stack.empty()) {
+ const StackEntry Entry = Stack.back();
+ Stack.pop_back();
+
+ // If the expression type is const-qualified at the appropriate indirection
+ // level then we can not mutate the object.
+ QualType Ty = Entry.E->getType().getCanonicalType();
+ for (int I = 0; I < Entry.Indirections; ++I) {
+ assert(Ty->isPointerType());
+ Ty = Ty->getPointeeType().getCanonicalType();
+ }
+ if (Ty.isConstQualified()) {
+ continue;
+ }
+
+ // Otherwise we have to look at the parents to see how the expression is
+ // used.
+ const auto Parents = Ctx.getParents(*Entry.E);
+ // Note: most nodes have a single parents, but there exist nodes that have
+ // several parents, such as `InitListExpr` that have semantic and syntactic
+ // forms.
+ for (const auto &Parent : Parents) {
+ if (Parent.get<CompoundStmt>()) {
+ // Unused block-scope statement.
+ continue;
+ }
+ const Expr *const P = Parent.get<Expr>();
+ if (P == nullptr) {
+ // `Parent` is not an expr (e.g. a `VarDecl`).
+ // The case of binding to a `const&` or `const*` variable is handled by
+ // the fact that there is going to be a `NoOp` cast to const below the
+ // `VarDecl`, so we're not even going to get there.
+ // The case of copying into a value-typed variable is handled by the
+ // rvalue cast.
+ // This triggers only when binding to a mutable reference/ptr variable.
+ // FIXME: When we take a mutable reference we could keep checking the
+ // new variable for const usage only.
+ return false;
+ }
+ // Cosmetic nodes.
+ if (isa<ParenExpr>(P) || isa<MaterializeTemporaryExpr>(P)) {
+ Stack.emplace_back(P, Entry.Indirections);
+ continue;
+ }
+ if (const auto *const Cast = dyn_cast<CastExpr>(P)) {
+ switch (Cast->getCastKind()) {
+ // NoOp casts are used to add `const`. We'll check whether adding that
+ // const prevents modification when we process the cast.
+ case CK_NoOp:
+ // These do nothing w.r.t. to mutability.
+ case CK_BaseToDerived:
+ case CK_DerivedToBase:
+ case CK_UncheckedDerivedToBase:
+ case CK_Dynamic:
+ case CK_BaseToDerivedMemberPointer:
+ case CK_DerivedToBaseMemberPointer:
+ Stack.emplace_back(Cast, Entry.Indirections);
+ continue;
+ case CK_ToVoid:
+ case CK_PointerToBoolean:
+ // These do not mutate the underlying variable.
+ continue;
+ case CK_LValueToRValue: {
+ // An rvalue is immutable.
+ if (Entry.Indirections == 0) {
+ continue;
+ }
+ Stack.emplace_back(Cast, Entry.Indirections);
+ continue;
+ }
+ default:
+ // Bail out on casts that we cannot analyze.
+ return false;
+ }
+ }
+ if (const auto *const Member = dyn_cast<MemberExpr>(P)) {
+ if (const auto *const Method =
+ dyn_cast<CXXMethodDecl>(Member->getMemberDecl())) {
+ if (!Method->isConst()) {
+ // The method can mutate our variable.
+ return false;
+ }
+ continue;
+ }
+ Stack.emplace_back(Member, 0);
+ continue;
+ }
+ if (const auto *const Op = dyn_cast<UnaryOperator>(P)) {
+ switch (Op->getOpcode()) {
+ case UO_AddrOf:
+ Stack.emplace_back(Op, Entry.Indirections + 1);
+ continue;
+ case UO_Deref:
+ assert(Entry.Indirections > 0);
+ Stack.emplace_back(Op, Entry.Indirections - 1);
+ continue;
+ default:
+ // Bail out on unary operators that we cannot analyze.
+ return false;
+ }
+ }
+
+ // Assume any other expression can modify the underlying variable.
+ return false;
+ }
+ }
+
+ // No parent can modify the variable.
+ return true;
+}
+
} // namespace
-// Finds all DeclRefExprs where a const method is called on VarDecl or VarDecl
-// is the a const reference or value argument to a CallExpr or CXXConstructExpr.
SmallPtrSet<const DeclRefExpr *, 16>
constReferenceDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt,
- ASTContext &Context) {
- auto DeclRefToVar =
- declRefExpr(to(varDecl(equalsNode(&VarDecl)))).bind("declRef");
- auto MemberExprOfVar = memberExpr(hasObjectExpression(DeclRefToVar));
- auto DeclRefToVarOrMemberExprOfVar =
- stmt(anyOf(DeclRefToVar, MemberExprOfVar));
- auto ConstMethodCallee = callee(cxxMethodDecl(isConst()));
- // Match method call expressions where the variable is referenced as the this
- // implicit object argument and operator call expression for member operators
- // where the variable is the 0-th argument.
- auto Matches = match(
- findAll(expr(anyOf(
- cxxMemberCallExpr(ConstMethodCallee,
- on(DeclRefToVarOrMemberExprOfVar)),
- cxxOperatorCallExpr(ConstMethodCallee,
- hasArgument(0, DeclRefToVarOrMemberExprOfVar))))),
- Stmt, Context);
+ ASTContext &Context, int Indirections) {
+ auto Matches = match(findAll(declRefExpr(to(varDecl(equalsNode(&VarDecl))),
+ doesNotMutateObject(Indirections))
+ .bind("declRef")),
+ Stmt, Context);
SmallPtrSet<const DeclRefExpr *, 16> DeclRefs;
extractNodesByIdTo(Matches, "declRef", DeclRefs);
- auto ConstReferenceOrValue =
- qualType(anyOf(matchers::isReferenceToConst(),
- unless(anyOf(referenceType(), pointerType(),
- substTemplateTypeParmType()))));
- auto ConstReferenceOrValueOrReplaced = qualType(anyOf(
- ConstReferenceOrValue,
- substTemplateTypeParmType(hasReplacementType(ConstReferenceOrValue))));
- auto UsedAsConstRefOrValueArg = forEachArgumentWithParam(
- DeclRefToVarOrMemberExprOfVar,
- parmVarDecl(hasType(ConstReferenceOrValueOrReplaced)));
- Matches = match(findAll(invocation(UsedAsConstRefOrValueArg)), Stmt, Context);
- extractNodesByIdTo(Matches, "declRef", DeclRefs);
- // References and pointers to const assignments.
- Matches = match(
- findAll(declStmt(has(varDecl(
- hasType(qualType(matchers::isReferenceToConst())),
- hasInitializer(ignoringImpCasts(DeclRefToVarOrMemberExprOfVar)))))),
- Stmt, Context);
- extractNodesByIdTo(Matches, "declRef", DeclRefs);
- Matches = match(findAll(declStmt(has(varDecl(
- hasType(qualType(matchers::isPointerToConst())),
- hasInitializer(ignoringImpCasts(unaryOperator(
- hasOperatorName("&"),
- hasUnaryOperand(DeclRefToVarOrMemberExprOfVar)))))))),
- Stmt, Context);
- extractNodesByIdTo(Matches, "declRef", DeclRefs);
+
return DeclRefs;
}
bool isOnlyUsedAsConst(const VarDecl &Var, const Stmt &Stmt,
- ASTContext &Context) {
+ ASTContext &Context, int Indirections) {
// Collect all DeclRefExprs to the loop variable and all CallExprs and
// CXXConstructExprs where the loop variable is used as argument to a const
// reference parameter.
// If the difference is empty it is safe for the loop variable to be a const
// reference.
auto AllDeclRefs = allDeclRefExprs(Var, Stmt, Context);
- auto ConstReferenceDeclRefs = constReferenceDeclRefExprs(Var, Stmt, Context);
+ auto ConstReferenceDeclRefs =
+ constReferenceDeclRefExprs(Var, Stmt, Context, Indirections);
return isSetDifferenceEmpty(AllDeclRefs, ConstReferenceDeclRefs);
}
diff --git a/clang-tools-extra/clang-tidy/utils/DeclRefExprUtils.h b/clang-tools-extra/clang-tidy/utils/DeclRefExprUtils.h
index 2c16d95408cf68..8361b9d89ed268 100644
--- a/clang-tools-extra/clang-tidy/utils/DeclRefExprUtils.h
+++ b/clang-tools-extra/clang-tidy/utils/DeclRefExprUtils.h
@@ -15,15 +15,6 @@
namespace clang::tidy::utils::decl_ref_expr {
-/// Returns true if all ``DeclRefExpr`` to the variable within ``Stmt``
-/// do not modify it.
-///
-/// Returns ``true`` if only const methods or operators are called on the
-/// variable or the variable is a const reference or value argument to a
-/// ``callExpr()``.
-bool isOnlyUsedAsConst(const VarDecl &Var, const Stmt &Stmt,
- ASTContext &Context);
-
/// Returns set of all ``DeclRefExprs`` to ``VarDecl`` within ``Stmt``.
llvm::SmallPtrSet<const DeclRefExpr *, 16>
allDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt, ASTContext &Context);
@@ -34,9 +25,31 @@ allDeclRefExprs(const VarDecl &VarDecl, const Decl &Decl, ASTContext &Context);
/// Returns set of all ``DeclRefExprs`` to ``VarDecl`` within ``Stmt`` where
/// ``VarDecl`` is guaranteed to be accessed in a const fashion.
+///
+/// If ``VarDecl`` is of pointer type, ``Indirections`` specifies the level
+/// of indirection of the object whose mutations we are tracking.
+///
+/// For example, given:
+/// ```
+/// int i;
+/// int* p;
+/// p = &i; // (A)
+/// *p = 3; // (B)
+/// ```
+///
+/// - `constReferenceDeclRefExprs(P, Stmt, Context, 0)` returns the reference
+// to `p` in (B): the pointee is modified, but the pointer is not;
+/// - `constReferenceDeclRefExprs(P, Stmt, Context, 1)` returns the reference
+// to `p` in (A): the pointee is modified, but the pointer is not;
llvm::SmallPtrSet<const DeclRefExpr *, 16>
constReferenceDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt,
- ASTContext &Context);
+ ASTContext &Context, int Indirections);
+
+/// Returns true if all ``DeclRefExpr`` to the variable within ``Stmt``
+/// do not modify it.
+/// See `constReferenceDeclRefExprs` for the meaning of ``Indirections``.
+bool isOnlyUsedAsConst(const VarDecl &Var, const Stmt &Stmt,
+ ASTContext &Context, int Indirections);
/// Returns ``true`` if ``DeclRefExpr`` is the argument of a copy-constructor
/// call expression within ``Decl``.
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index a0b9fcfe0d7774..79eb777afee575 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -179,6 +179,12 @@ Changes in existing checks
<clang-tidy/checks/modernize/use-override>` check to also remove any trailing
whitespace when deleting the ``virtual`` keyword.
+- Improved :doc:`performance-unnecessary-copy-initialization
+ <clang-tidy/checks/performance/unnecessary-copy-initialization>` check by
+ detecting more cases of constant access. In particular, pointers can be
+ analyzed, se the check now handles the common patterns
+ `const auto e = (*vector_ptr)[i]` and `const auto e = vector_ptr->at(i);`.
+
- Improved :doc:`readability-implicit-bool-conversion
<clang-tidy/checks/readability/implicit-bool-conversion>` check to provide
valid fix suggestions for ``static_cast`` without a preceding space and
diff --git a/clang-tools-extra/test/clang-tidy/checkers/performance/unnecessary-copy-initialization.cpp b/clang-tools-extra/test/clang-tidy/checkers/performance/unnecessary-copy-initialization.cpp
index 049ba64d6aedeb..92625cc1332e28 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/performance/unnecessary-copy-initialization.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/performance/unnecessary-copy-initialization.cpp
@@ -41,6 +41,10 @@ struct Container {
ConstIterator<T> end() const;
void nonConstMethod();
bool constMethod() const;
+
+ const T& at(int) const;
+ T& at(int);
+
};
using ExpensiveToCopyContainerAlias = Container<ExpensiveToCopyType>;
@@ -225,25 +229,25 @@ void PositiveOperatorCallConstValueParamAlias(const ExpensiveToCopyContainerAlia
VarCopyConstructed.constMethod();
}
-void PositiveOperatorCallConstValueParam(const Container<ExpensiveToCopyType>* C) {
+void PositiveOperatorCallConstPtrParam(const Container<ExpensiveToCopyType>* C) {
const auto AutoAssigned = (*C)[42];
- // TODO-MESSAGES: [[@LINE-1]]:14: warning: the const qualified variable 'AutoAssigned'
- // TODO-FIXES: const auto& AutoAssigned = (*C)[42];
+ // CHECK-MESSAGES: [[@LINE-1]]:14: warning: the const qualified variable 'AutoAssigned'
+ // CHECK-FIXES: const auto& AutoAssigned = (*C)[42];
AutoAssigned.constMethod();
const auto AutoCopyConstructed((*C)[42]);
- // TODO-MESSAGES: [[@LINE-1]]:14: warning: the const qualified variable 'AutoCopyConstructed'
- // TODO-FIXES: const auto& AutoCopyConstructed((*C)[42]);
+ // CHECK-MESSAGES: [[@LINE-1]]:14: warning: the const qualified variable 'AutoCopyConstructed'
+ // CHECK-FIXES: const auto& AutoCopyConstructed((*C)[42]);
AutoCopyConstructed.constMethod();
- const ExpensiveToCopyType VarAssigned = C->operator[](42);
- // TODO-MESSAGES: [[@LINE-1]]:29: warning: the const qualified variable 'VarAssigned'
- // TODO-FIXES: const ExpensiveToCopyType& VarAssigned = C->operator[](42);
+ const ExpensiveToCopyType VarAssigned = C->at(42);
+ // CHECK-MESSAGES: [[@LINE-1]]:29: warning: the const qualified variable 'VarAssigned'
+ // CHECK-FIXES: const ExpensiveToCopyType& VarAssigned = C->at(42);
VarAssigned.constMethod();
- const ExpensiveToCopyType VarCopyConstructed(C->operator[](42));
- // TODO-MESSAGES: [[@LINE-1]]:29: warning: the const qualified variable 'VarCopyConstructed'
- // TODO-FIXES: const ExpensiveToCopyType& VarCopyConstructed(C->operator[](42));
+ const ExpensiveToCopyType VarCopyConstructed(C->at(42));
+ // CHECK-MESSAGES: [[@LINE-1]]:29: warning: the const qualified variable 'VarCopyConstructed'
+ // CHECK-FIXES: const ExpensiveToCopyType& VarCopyConstructed(C->at(42));
VarCopyConstructed.constMethod();
}
@@ -876,3 +880,4 @@ void negativeNonConstMemberExpr() {
mutate(&Copy.Member);
}
}
+
diff --git a/clang-tools-extra/unittests/clang-tidy/DeclRefExprUtilsTest.cpp b/clang-tools-extra/unittests/clang-tidy/DeclRefExprUtilsTest.cpp
index a653b11faad282..4c9e81ea0f61ac 100644
--- a/clang-tools-extra/unittests/clang-tidy/DeclRefExprUtilsTest.cpp
+++ b/clang-tools-extra/unittests/clang-tidy/DeclRefExprUtilsTest.cpp
@@ -12,6 +12,7 @@ namespace tidy {
namespace {
using namespace clang::ast_matchers;
+template <int Indirections>
class ConstReferenceDeclRefExprsTransform : public ClangTidyCheck {
public:
ConstReferenceDeclRefExprsTransform(StringRef CheckName,
@@ -27,7 +28,7 @@ class ConstReferenceDeclRefExprsTransform : public ClangTidyCheck {
using utils::decl_ref_expr::constReferenceDeclRefExprs;
const auto const_decrefexprs = constReferenceDeclRefExprs(
*D, *cast<FunctionDecl>(D->getDeclContext())->getBody(),
- *Result.Context);
+ *Result.Context, Indirections);
for (const DeclRefExpr *const Expr : const_decrefexprs) {
assert(Expr);
@@ -40,7 +41,7 @@ class ConstReferenceDeclRefExprsTransform : public ClangTidyCheck {
namespace test {
-void RunTest(StringRef Snippet) {
+template <int Indirections> void RunTest(StringRef Snippet) {
StringRef CommonCode = R"(
struct ConstTag{};
@@ -59,6 +60,9 @@ void RunTest(StringRef Snippet) {
bool operator==(const S&) const;
int int_member;
+ // We consider a mutation of the `*ptr_member` to be a const use of
+ // `*this`. This is consistent with the semantics of `const`-qualified
+ // methods, which prevent modifying `ptr_member` but not `*ptr_member`.
int* ptr_member;
};
@@ -92,69 +96,88 @@ void RunTest(StringRef Snippet) {
llvm::SmallVector<StringRef, 1> Parts;
StringRef(Code).split(Parts, "/*const*/");
- EXPECT_EQ(Code, runCheckOnCode<ConstReferenceDeclRefExprsTransform>(
- join(Parts, "")));
+ EXPECT_EQ(Code,
+ runCheckOnCode<ConstReferenceDeclRefExprsTransform<Indirections>>(
+ join(Parts, "")));
}
TEST(ConstReferenceDeclRefExprsTest, ConstValueVar) {
- RunTest(R"(
+ RunTest<0>(R"(
void f(const S target) {
useVal(/*const*/target);
useConstRef(/*const*/target);
- useConstPtr(&target);
- useConstPtrConstRef(&target);
+ useConstPtr(&/*const*/target);
+ useConstPtrConstRef(&/*const*/target);
/*const*/target.constMethod();
/*const*/target(ConstTag{});
/*const*/target[42];
useConstRef((/*const*/target));
(/*const*/target).constMethod();
(void)(/*const*/target == /*const*/target);
- (void)target;
- (void)⌖
- (void)*⌖
+ (void)/*const*/target;
+ (void)&/*const*/target;
+ (void)*&/*const*/target;
+ /*const*/target;
S copy1 = /*const*/target;
S copy2(/*const*/target);
+ /*const*/target.int_member;
useInt(/*const*/target.int_member);
useIntConstRef(/*const*/target.int_member);
- useIntPtr(target.ptr_member);
- useIntConstPtr(&target.int_member);
+ useIntPtr(/*const*/target.ptr_member);
+ useIntConstPtr(&/*const*/target.int_member);
+
+ const S& const_target_ref = /*const*/target;
+ const S* const_target_ptr = &/*const*/target;
}
)");
}
TEST(ConstReferenceDeclRefExprsTest, ConstRefVar) {
- RunTest(R"(
+ RunTest<0>(R"(
void f(const S& target) {
useVal(/*const*/target);
useConstRef(/*const*/target);
- useConstPtr(&target);
- useConstPtrConstRef(&target);
+ useConstPtr(&/*const*/target);
+ useConstPtrConstRef(&/*const*/target);
/*const*/target.constMethod();
/*const*/target(ConstTag{});
/*const*/target[42];
useConstRef((/*const*/target));
(/*const*/target).constMethod();
(void)(/*const*/target == /*const*/target);
- (void)target;
- (void)⌖
- (void)*⌖
+ (void)/*const*/target;
+ (void)&/*const*/target;
+ (void)*&/*const*/target;
+ /*const*/target;
S copy1 = /*const*/target;
S copy2(/*const*/target);
+ /*const*/target.int_member;
useInt(/*const*/target.int_member);
useIntConstRef(/*const*/target.int_member);
- useIntPtr(target.ptr_member);
- useIntConstPtr(&target.int_member);
+ useIntPtr(/*const*/target.ptr_member);
+ useIntConstPtr(&/*const*/target.int_member);
+
+ const S& const_target_ref = /*const*/target;
+ const S* const_target_ptr = &/*const*/target;
+ }
+)");
+}
+
+TEST(ConstReferenceDeclRefExprsTest, DEBUGREMOVEME) {
+ RunTest<0>(R"(
+ void f(S target, const S& other) {
+ S* target_ptr = ⌖
}
)");
}
TEST(ConstReferenceDeclRefExprsTest, ValueVar) {
- RunTest(R"(
+ RunTest<0>(R"(
void f(S target, const S& other) {
useConstRef(/*const*/target);
useVal(/*const*/target);
- useConstPtr(&target);
- useConstPtrConstRef(&target);
+ useConstPtr(&/*const*/target);
+ useConstPtrConstRef(&/*const*/target);
/*const*/target.constMethod();
target.nonConstMethod();
/*const*/target(ConstTag{});
@@ -167,26 +190,33 @@ TEST(ConstReferenceDeclRefExprsTest, ValueVar) {
(/*const*/target).constMethod();
(void)(/*const*/target == /*const*/target);
(void)(/*const*/target == other);
- (void)target;
- (void)⌖
- (void)*⌖
+ (void)/*const*/target;
+ (void)&/*const*/target;
+ (void)*&/*const*/target;
+ /*const*/target;
S copy1 = /*const*/target;
S copy2(/*const*/target);
+ /*const*/target.int_member;
useInt(/*const*/target.int_member);
useIntConstRef(/*const*/target.int_member);
- useIntPtr(target.ptr_member);
- useIntConstPtr(&target.int_member);
+ useIntPtr(/*const*/target.ptr_member);
+ useIntConstPtr(&/*const*/target.int_member);
+
+ const S& const_target_ref = /*const*/target;
+ const S* const_target_ptr = &/*const*/target;
+ S* target_ptr = ⌖
}
)");
}
TEST(ConstReferenceDeclRefExprsTest, RefVar) {
- RunTest(R"(
+ RunTest<0>(R"(
void f(S& target) {
useVal(/*const*/target);
+ usePtr(&target);
useConstRef(/*const*/target);
- useConstPtr(&target);
- useConstPtrConstRef(&target);
+ useConstPtr(&/*const*/target);
+ useConstPtrConstRef(&/*const*/target);
/*const*/target.constMethod();
target.nonConstMethod();
/*const*/target(ConstTag{});
@@ -194,118 +224,150 @@ TEST(ConstReferenceDeclRefExprsTest, RefVar) {
useConstRef((/*const*/target));
(/*const*/target).constMethod();
(void)(/*const*/target == /*const*/target);
- (void)target;
- (void)⌖
- (void)*⌖
+ (void)/*const*/target;
+ (void)&/*const*/target;
+ (void)*&/*const*/target;
+ /*const*/target;
S copy1 = /*const*/target;
S copy2(/*const*/target);
+ /*const*/target.int_member;
useInt(/*const*/target.int_member);
useIntConstRef(/*const*/target.int_member);
- useIntPtr(target.ptr_member);
- useIntConstPtr(&target.int_member);
+ useIntPtr(/*const*/target.ptr_member);
+ useIntConstPtr(&/*const*/target.int_member);
+
+ (void)(&/*const*/target)->int_member;
+ useIntRef((&target)->int_member);
+
+ const S& const_target_ref = /*const*/target;
+ const S* const_target_ptr = &/*const*/target;
+ S* target_ptr = ⌖
}
)");
}
TEST(ConstReferenceDeclRefExprsTest, PtrVar) {
- RunTest(R"(
+ RunTest<1>(R"(
void f(S* target) {
- useVal(*target);
- useConstRef(*target);
- useConstPtr(target);
+ useVal(*/*const*/target);
+ usePtr(target);
+ useConstRef(*/*const*/target);
+ useConstPtr(/*const*/target);
useConstPtrConstRef(/*const*/target);
+ usePtrConstPtr(&target);
/*const*/target->constMethod();
target->nonConstMethod();
- (*target)(ConstTag{});
+ (*/*const*/target)(ConstTag{});
(*target)[42];
target->operator[](42);
- useConstRef((*target));
+ useConstRef((*/*const*/target));
(/*const*/target)->constMethod();
- (void)(*target == *target);
- (void)*target;
- (void)target;
- S copy1 = *target;
- S copy2(*target);
- useInt(target->int_member);
- useIntConstRef(target->int_member);
- useIntPtr(target->ptr_member);
- useIntConstPtr(&target->int_member);
+ (void)(*/*const*/target == */*const*/target);
+ (void)*/*const*/target;
+ (void)/*const*/target;
+ /*const*/target;
+ S copy1 = */*const*/target;
+ S copy2(*/*const*/target);
+ /*const*/target->int_member;
+ useInt(/*const*/target->int_member);
+ useIntConstRef(/*const*/target->int_member);
+ useIntPtr(/*const*/target->ptr_member);
+ useIntConstPtr(&/*const*/target->int_member);
+
+ const S& const_target_ref = */*const*/target;
+ const S* const_target_ptr = /*const*/target;
+ S* target_ptr = target; // FIXME: we could chect const usage of `target_ptr`.
}
)");
}
TEST(ConstReferenceDeclRefExprsTest, ConstPtrVar) {
- RunTest(R"(
+ RunTest<1>(R"(
void f(const S* target) {
- useVal(*target);
- useConstRef(*target);
- useConstPtr(target);
- useConstPtrRef(target);
- useConstPtrPtr(&target);
- useConstPtrConstPtr(&target);
+ useVal(*/*const*/target);
+ useConstRef(*/*const*/target);
+ useConstPtr(/*const*/target);
+ useConstPtrRef(/*const*/target);
+ useConstPtrPtr(&/*const*/target);
+ useConstPtrConstPtr(&/*const*/target);
useConstPtrConstRef(/*const*/target);
/*const*/target->constMethod();
- (*target)(ConstTag{});
- (*target)[42];
+ (*/*const*/target)(ConstTag{});
+ (*/*const*/target)[42];
/*const*/target->operator[](42);
- (void)(*target == *target);
- (void)target;
- (void)*target;
- if(target) {}
- S copy1 = *target;
- S copy2(*target);
- useInt(target->int_member);
- useIntConstRef(target->int_member);
- useIntPtr(target->ptr_member);
- useIntConstPtr(&target->int_member);
+ (void)(*/*const*/target == */*const*/target);
+ (void)/*const*/target;
+ (void)*/*const*/target;
+ /*const*/target;
+ if(/*const*/target) {}
+ S copy1 = */*const*/target;
+ S copy2(*/*const*/target);
+ /*const*/target->int_member;
+ useInt(/*const*/target->int_member);
+ useIntConstRef(/*const*/target->int_member);
+ useIntPtr(/*const*/target->ptr_member);
+ useIntConstPtr(&/*const*/target->int_member);
+
+ const S& const_target_ref = */*const*/target;
+ const S* const_target_ptr = /*const*/target;
}
)");
}
TEST(ConstReferenceDeclRefExprsTest, ConstPtrPtrVar) {
- RunTest(R"(
+ RunTest<2>(R"(
void f(const S** target) {
- useVal(**target);
- useConstRef(**target);
- useConstPtr(*target);
- useConstPtrRef(*target);
- useConstPtrPtr(target);
- useConstPtrConstPtr(target);
- useConstPtrConstRef(*target);
- (void)target;
- (void)*target;
- (void)**target;
- if(target) {}
- if(*target) {}
- S copy1 = **target;
- S copy2(**target);
- useInt((*target)->int_member);
- useIntConstRef((*target)->int_member);
- useIntPtr((*target)->ptr_member);
- useIntConstPtr(&(*target)->int_member);
+ useVal(**/*const*/target);
+ useConstRef(**/*const*/target);
+ useConstPtr(*/*const*/target);
+ useConstPtrRef(*/*const*/target);
+ useConstPtrPtr(/*const*/target);
+ useConstPtrConstPtr(/*const*/target);
+ useConstPtrConstRef(*/*const*/target);
+ (void)/*const*/target;
+ (void)*/*const*/target;
+ (void)**/*const*/target;
+ /*const*/target;
+ if(/*const*/target) {}
+ if(*/*const*/target) {}
+ S copy1 = **/*const*/target;
+ S copy2(**/*const*/target);
+ (*/*const*/target)->int_member;
+ useInt((*/*const*/target)->int_member);
+ useIntConstRef((*/*const*/target)->int_member);
+ useIntPtr((*/*const*/target)->ptr_member);
+ useIntConstPtr(&(*/*const*/target)->int_member);
+
+ const S& const_target_ref = **/*const*/target;
+ const S* const_target_ptr = */*const*/target;
}
)");
}
TEST(ConstReferenceDeclRefExprsTest, ConstPtrConstPtrVar) {
- RunTest(R"(
+ RunTest<2>(R"(
void f(const S* const* target) {
- useVal(**target);
- useConstRef(**target);
- useConstPtr(*target);
- useConstPtrConstPtr(target);
- useConstPtrConstRef(*target);
- (void)target;
- (void)target;
- (void)**target;
- if(target) {}
- if(*target) {}
- S copy1 = **target;
- S copy2(**target);
- useInt((*target)->int_member);
- useIntConstRef((*target)->int_member);
- useIntPtr((*target)->ptr_member);
- useIntConstPtr(&(*target)->int_member);
+ useVal(**/*const*/target);
+ useConstRef(**/*const*/target);
+ useConstPtr(*/*const*/target);
+ useConstPtrConstPtr(/*const*/target);
+ useConstPtrConstRef(*/*const*/target);
+ (void)/*const*/target;
+ (void)*/*const*/target;
+ (void)**/*const*/target;
+ /*const*/target;
+ if(/*const*/target) {}
+ if(*/*const*/target) {}
+ S copy1 = **/*const*/target;
+ S copy2(**/*const*/target);
+ (*/*const*/target)->int_member;
+ useInt((*/*const*/target)->int_member);
+ useIntConstRef((*/*const*/target)->int_member);
+ useIntPtr((*/*const*/target)->ptr_member);
+ useIntConstPtr(&(*/*const*/target)->int_member);
+
+ const S& const_target_ref = **/*const*/target;
+ const S* const_target_ptr = */*const*/target;
}
)");
}
>From 09f6a7bd538abf6575dc933f59f80a19fabe0e25 Mon Sep 17 00:00:00 2001
From: Clement Courbet <courbet at google.com>
Date: Mon, 26 Feb 2024 08:36:21 +0000
Subject: [PATCH 4/5] Spell types explicitly (no auto).
---
clang-tools-extra/clang-tidy/utils/DeclRefExprUtils.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/utils/DeclRefExprUtils.cpp b/clang-tools-extra/clang-tidy/utils/DeclRefExprUtils.cpp
index 663691c519b8e9..c6e01d2ecd8284 100644
--- a/clang-tools-extra/clang-tidy/utils/DeclRefExprUtils.cpp
+++ b/clang-tools-extra/clang-tidy/utils/DeclRefExprUtils.cpp
@@ -77,7 +77,7 @@ AST_MATCHER_P(DeclRefExpr, doesNotMutateObject, int, Indirections) {
llvm::SmallVector<StackEntry, 4> Stack;
Stack.emplace_back(&Node, Indirections);
- auto &Ctx = Finder->getASTContext();
+ ASTContext &Ctx = Finder->getASTContext();
while (!Stack.empty()) {
const StackEntry Entry = Stack.back();
@@ -96,7 +96,7 @@ AST_MATCHER_P(DeclRefExpr, doesNotMutateObject, int, Indirections) {
// Otherwise we have to look at the parents to see how the expression is
// used.
- const auto Parents = Ctx.getParents(*Entry.E);
+ const DynTypedNodeList Parents = Ctx.getParents(*Entry.E);
// Note: most nodes have a single parents, but there exist nodes that have
// several parents, such as `InitListExpr` that have semantic and syntactic
// forms.
>From 4a6aaf65d58b37e10553ca0421d4a9385602329e Mon Sep 17 00:00:00 2001
From: Clement Courbet <courbet at google.com>
Date: Mon, 26 Feb 2024 08:37:58 +0000
Subject: [PATCH 5/5] remove braces in single-line conditions
---
clang-tools-extra/clang-tidy/utils/DeclRefExprUtils.cpp | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/utils/DeclRefExprUtils.cpp b/clang-tools-extra/clang-tidy/utils/DeclRefExprUtils.cpp
index c6e01d2ecd8284..f0ffa517047b27 100644
--- a/clang-tools-extra/clang-tidy/utils/DeclRefExprUtils.cpp
+++ b/clang-tools-extra/clang-tidy/utils/DeclRefExprUtils.cpp
@@ -90,9 +90,8 @@ AST_MATCHER_P(DeclRefExpr, doesNotMutateObject, int, Indirections) {
assert(Ty->isPointerType());
Ty = Ty->getPointeeType().getCanonicalType();
}
- if (Ty.isConstQualified()) {
+ if (Ty.isConstQualified())
continue;
- }
// Otherwise we have to look at the parents to see how the expression is
// used.
@@ -143,9 +142,8 @@ AST_MATCHER_P(DeclRefExpr, doesNotMutateObject, int, Indirections) {
continue;
case CK_LValueToRValue: {
// An rvalue is immutable.
- if (Entry.Indirections == 0) {
+ if (Entry.Indirections == 0)
continue;
- }
Stack.emplace_back(Cast, Entry.Indirections);
continue;
}
More information about the cfe-commits
mailing list