Skip to content

Commit

Permalink
merge main into amd-staging
Browse files Browse the repository at this point in the history
Change-Id: If3a54245df1de7dc1a7b28f6bbd850b47b6d6125
  • Loading branch information
Jenkins committed Jun 10, 2024
2 parents 66a2171 + 8dc8b9f commit 0e9ca91
Show file tree
Hide file tree
Showing 182 changed files with 2,468 additions and 3,481 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,16 @@ void recordRemoval(const DeclStmt &Stmt, ASTContext &Context,
}
}

AST_MATCHER_FUNCTION_P(StatementMatcher, isConstRefReturningMethodCall,
AST_MATCHER_FUNCTION_P(StatementMatcher,
isRefReturningMethodCallWithConstOverloads,
std::vector<StringRef>, ExcludedContainerTypes) {
// Match method call expressions where the `this` argument is only used as
// const, this will be checked in `check()` part. This returned const
// reference is highly likely to outlive the local const reference of the
// variable being declared. The assumption is that the const reference being
// returned either points to a global static variable or to a member of the
// called object.
// const, this will be checked in `check()` part. This returned reference is
// highly likely to outlive the local const reference of the variable being
// declared. The assumption is that the reference being returned either points
// to a global static variable or to a member of the called object.
const auto MethodDecl =
cxxMethodDecl(returns(hasCanonicalType(matchers::isReferenceToConst())))
cxxMethodDecl(returns(hasCanonicalType(referenceType())))
.bind(MethodDeclId);
const auto ReceiverExpr =
ignoringParenImpCasts(declRefExpr(to(varDecl().bind(ObjectArgId))));
Expand Down Expand Up @@ -121,7 +121,7 @@ AST_MATCHER_FUNCTION_P(StatementMatcher, initializerReturnsReferenceToConst,
declRefExpr(to(varDecl(hasLocalStorage()).bind(OldVarDeclId)));
return expr(
anyOf(isConstRefReturningFunctionCall(),
isConstRefReturningMethodCall(ExcludedContainerTypes),
isRefReturningMethodCallWithConstOverloads(ExcludedContainerTypes),
ignoringImpCasts(OldVarDeclRef),
ignoringImpCasts(unaryOperator(hasOperatorName("&"),
hasUnaryOperand(OldVarDeclRef)))));
Expand Down Expand Up @@ -259,10 +259,11 @@ void UnnecessaryCopyInitialization::registerMatchers(MatchFinder *Finder) {
.bind("blockStmt");
};

Finder->addMatcher(LocalVarCopiedFrom(anyOf(isConstRefReturningFunctionCall(),
isConstRefReturningMethodCall(
ExcludedContainerTypes))),
this);
Finder->addMatcher(
LocalVarCopiedFrom(anyOf(
isConstRefReturningFunctionCall(),
isRefReturningMethodCallWithConstOverloads(ExcludedContainerTypes))),
this);

Finder->addMatcher(LocalVarCopiedFrom(declRefExpr(
to(varDecl(hasLocalStorage()).bind(OldVarDeclId)))),
Expand Down
168 changes: 161 additions & 7 deletions clang-tools-extra/clang-tidy/utils/DeclRefExprUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,116 @@ void extractNodesByIdTo(ArrayRef<BoundNodes> Matches, StringRef ID,
Nodes.insert(Match.getNodeAs<Node>(ID));
}

// Returns true if both types refer to the same type,
// ignoring the const-qualifier.
bool isSameTypeIgnoringConst(QualType A, QualType B) {
A = A.getCanonicalType();
B = B.getCanonicalType();
A.addConst();
B.addConst();
return A == B;
}

// Returns true if `D` and `O` have the same parameter types.
bool hasSameParameterTypes(const CXXMethodDecl &D, const CXXMethodDecl &O) {
if (D.getNumParams() != O.getNumParams())
return false;
for (int I = 0, E = D.getNumParams(); I < E; ++I) {
if (!isSameTypeIgnoringConst(D.getParamDecl(I)->getType(),
O.getParamDecl(I)->getType()))
return false;
}
return true;
}

// If `D` has a const-qualified overload with otherwise identical
// ref-qualifiers and parameter types, returns that overload.
const CXXMethodDecl *findConstOverload(const CXXMethodDecl &D) {
assert(!D.isConst());

DeclContext::lookup_result LookupResult =
D.getParent()->lookup(D.getNameInfo().getName());
if (LookupResult.isSingleResult()) {
// No overload.
return nullptr;
}
for (const Decl *Overload : LookupResult) {
const auto *O = dyn_cast<CXXMethodDecl>(Overload);
if (O && !O->isDeleted() && O->isConst() &&
O->getRefQualifier() == D.getRefQualifier() &&
hasSameParameterTypes(D, *O))
return O;
}
return nullptr;
}

// Returns true if both types are pointers or reference to the same type,
// ignoring the const-qualifier.
bool pointsToSameTypeIgnoringConst(QualType A, QualType B) {
assert(A->isPointerType() || A->isReferenceType());
assert(B->isPointerType() || B->isReferenceType());
return isSameTypeIgnoringConst(A->getPointeeType(), B->getPointeeType());
}

// Return true if non-const member function `M` likely does not mutate `*this`.
//
// Note that if the member call selects a method/operator `f` that
// is not const-qualified, then we also consider that the object is
// not mutated if:
// - (A) there is a const-qualified overload `cf` of `f` that has
// the
// same ref-qualifiers;
// - (B) * `f` returns a value, or
// * if `f` returns a `T&`, `cf` returns a `const T&` (up to
// possible aliases such as `reference` and
// `const_reference`), or
// * if `f` returns a `T*`, `cf` returns a `const T*` (up to
// possible aliases).
// - (C) the result of the call is not mutated.
//
// The assumption that `cf` has the same semantics as `f`.
// For example:
// - In `std::vector<T> v; const T t = v[...];`, we consider that
// expression `v[...]` does not mutate `v` as
// `T& std::vector<T>::operator[]` has a const overload
// `const T& std::vector<T>::operator[] const`, and the
// result expression of type `T&` is only used as a `const T&`;
// - In `std::map<K, V> m; V v = m.at(...);`, we consider
// `m.at(...)` to be an immutable access for the same reason.
// However:
// - In `std::map<K, V> m; const V v = m[...];`, We consider that
// `m[...]` mutates `m` as `V& std::map<K, V>::operator[]` does
// not have a const overload.
// - In `std::vector<T> v; T& t = v[...];`, we consider that
// expression `v[...]` mutates `v` as the result is kept as a
// mutable reference.
//
// This function checks (A) ad (B), but the caller should make sure that the
// object is not mutated through the return value.
bool isLikelyShallowConst(const CXXMethodDecl &M) {
assert(!M.isConst());
// The method can mutate our variable.

// (A)
const CXXMethodDecl *ConstOverload = findConstOverload(M);
if (ConstOverload == nullptr) {
return false;
}

// (B)
const QualType CallTy = M.getReturnType().getCanonicalType();
const QualType OverloadTy = ConstOverload->getReturnType().getCanonicalType();
if (CallTy->isReferenceType()) {
return OverloadTy->isReferenceType() &&
pointsToSameTypeIgnoringConst(CallTy, OverloadTy);
}
if (CallTy->isPointerType()) {
return OverloadTy->isPointerType() &&
pointsToSameTypeIgnoringConst(CallTy, OverloadTy);
}
return isSameTypeIgnoringConst(CallTy, OverloadTy);
}

// 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
Expand All @@ -54,16 +164,15 @@ void extractNodesByIdTo(ArrayRef<BoundNodes> Matches, StringRef ID,
// 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
// We walk up the parents of the DeclRefExpr recursively. There are a few
// kinds of expressions:
// - Those that cannot be used to mutate the underlying variable. We can stop
// recursion there.
// - Those that can be used to mutate the underlying object in analyzable
// - Those that can be used to mutate the underlying variable 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.
// we assume that they can modify the expression.

struct StackEntry {
StackEntry(const Expr *E, int Indirections)
Expand All @@ -90,7 +199,7 @@ AST_MATCHER_P(DeclRefExpr, doesNotMutateObject, int, Indirections) {
assert(Ty->isPointerType());
Ty = Ty->getPointeeType().getCanonicalType();
}
if (Ty.isConstQualified())
if (Ty->isVoidType() || Ty.isConstQualified())
continue;

// Otherwise we have to look at the parents to see how the expression is
Expand Down Expand Up @@ -159,11 +268,56 @@ AST_MATCHER_P(DeclRefExpr, doesNotMutateObject, int, Indirections) {
// The method call cannot mutate our variable.
continue;
}
if (isLikelyShallowConst(*Method)) {
// We still have to check that the object is not modified through
// the method's return value (C).
const auto MemberParents = Ctx.getParents(*Member);
assert(MemberParents.size() == 1);
const auto *Call = MemberParents[0].get<CallExpr>();
// If `o` is an object of class type and `f` is a member function,
// then `o.f` has to be used as part of a call expression.
assert(Call != nullptr && "member function has to be called");
Stack.emplace_back(
Call,
Method->getReturnType().getCanonicalType()->isPointerType()
? 1
: 0);
continue;
}
return false;
}
Stack.emplace_back(Member, 0);
continue;
}
if (const auto *const OpCall = dyn_cast<CXXOperatorCallExpr>(P)) {
// Operator calls have function call syntax. The `*this` parameter
// is the first parameter.
if (OpCall->getNumArgs() == 0 || OpCall->getArg(0) != Entry.E) {
return false;
}
const auto *const Method =
dyn_cast<CXXMethodDecl>(OpCall->getDirectCallee());

if (Method == nullptr) {
// This is not a member operator. Typically, a friend operator. These
// are handled like function calls.
return false;
}

if (Method->isConst() || Method->isStatic()) {
continue;
}
if (isLikelyShallowConst(*Method)) {
// We still have to check that the object is not modified through
// the operator's return value (C).
Stack.emplace_back(
OpCall,
Method->getReturnType().getCanonicalType()->isPointerType() ? 1
: 0);
continue;
}
return false;
}

if (const auto *const Op = dyn_cast<UnaryOperator>(P)) {
switch (Op->getOpcode()) {
Expand Down
4 changes: 3 additions & 1 deletion clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -376,8 +376,10 @@ Changes in existing checks
- 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
analyzed, so the check now handles the common patterns
`const auto e = (*vector_ptr)[i]` and `const auto e = vector_ptr->at(i);`.
Calls to mutable function where there exists a `const` overload are also
handled.

- Improved :doc:`readability-avoid-return-with-void-value
<clang-tidy/checks/readability/avoid-return-with-void-value>` check by adding
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ struct ExpensiveToCopyType {

template <typename T>
struct Container {
using reference = T&;
using const_reference = const T&;

bool empty() const;
const T& operator[](int) const;
const T& operator[](int);
Expand All @@ -42,8 +45,8 @@ struct Container {
void nonConstMethod();
bool constMethod() const;

const T& at(int) const;
T& at(int);
reference at(int) const;
const_reference at(int);

};

Expand Down Expand Up @@ -207,6 +210,28 @@ void PositiveOperatorCallConstValueParam(const Container<ExpensiveToCopyType> C)
VarCopyConstructed.constMethod();
}

void PositiveOperatorValueParam(Container<ExpensiveToCopyType> C) {
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]);
// CHECK-MESSAGES: [[@LINE-1]]:14: warning: the const qualified variable 'AutoCopyConstructed'
// CHECK-FIXES: const auto& AutoCopyConstructed(C[42]);
AutoCopyConstructed.constMethod();

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.at(42));
// CHECK-MESSAGES: [[@LINE-1]]:29: warning: the const qualified variable 'VarCopyConstructed'
// CHECK-FIXES: const ExpensiveToCopyType& VarCopyConstructed(C.at(42));
VarCopyConstructed.constMethod();
}

void PositiveOperatorCallConstValueParamAlias(const ExpensiveToCopyContainerAlias C) {
const auto AutoAssigned = C[42];
// CHECK-MESSAGES: [[@LINE-1]]:14: warning: the const qualified variable 'AutoAssigned'
Expand Down
Loading

0 comments on commit 0e9ca91

Please sign in to comment.