================ @@ -36,6 +36,111 @@ void extractNodesByIdTo(ArrayRef<BoundNodes> Matches, StringRef ID, Nodes.insert(Match.getNodeAs<Node>(ID)); } +// If `D` has a const-qualified overload with otherwise identical +// ref-qualifiers, returns that overload. +const CXXMethodDecl *findConstOverload(const CXXMethodDecl &D) { + assert(!D.isConst()); + + DeclContext::lookup_result lookup_result = + D.getParent()->lookup(D.getNameInfo().getName()); + if (lookup_result.isSingleResult()) { + // No overload. + return nullptr; + } + for (const Decl *overload : lookup_result) { + const CXXMethodDecl *candidate = dyn_cast<CXXMethodDecl>(overload); + if (candidate && !candidate->isDeleted() && candidate->isConst() && + candidate->getRefQualifier() == D.getRefQualifier()) { + return candidate; + } + } + return nullptr; +} + +// Returns true if both types refer to the same 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 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()) { + if (!(OverloadTy->isReferenceType() && ---------------- fberger wrote:
I would find: `(!isReferenceType() || !pointsToSameTypIgnoringConst(....))` easier to parse here. https://github.com/llvm/llvm-project/pull/94362 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits