static constexpr StringRef ObjectArgId = "objectArg";
static constexpr StringRef InitFunctionCallId = "initFunctionCall";
+static constexpr StringRef MethodDeclId = "methodDecl";
+static constexpr StringRef FunctionDeclId = "functionDecl";
static constexpr StringRef OldVarDeclId = "oldVarDecl";
void recordFixes(const VarDecl &Var, ASTContext &Context,
// returned either points to a global static variable or to a member of the
// called object.
return cxxMemberCallExpr(
- callee(cxxMethodDecl(returns(matchers::isReferenceToConst()))),
+ callee(cxxMethodDecl(returns(matchers::isReferenceToConst()))
+ .bind(MethodDeclId)),
on(declRefExpr(to(varDecl().bind(ObjectArgId)))));
}
// Only allow initialization of a const reference from a free function if it
// has no arguments. Otherwise it could return an alias to one of its
// arguments and the arguments need to be checked for const use as well.
- return callExpr(callee(functionDecl(returns(matchers::isReferenceToConst()))),
+ return callExpr(callee(functionDecl(returns(matchers::isReferenceToConst()))
+ .bind(FunctionDeclId)),
argumentCountIs(0), unless(callee(cxxMethodDecl())))
.bind(InitFunctionCallId);
}
return allDeclRefExprs(Var, BlockStmt, Context).empty();
}
+const SubstTemplateTypeParmType *getSubstitutedType(const QualType &Type,
+ ASTContext &Context) {
+ auto Matches = match(
+ qualType(anyOf(substTemplateTypeParmType().bind("subst"),
+ hasDescendant(substTemplateTypeParmType().bind("subst")))),
+ Type, Context);
+ return selectFirst<SubstTemplateTypeParmType>("subst", Matches);
+}
+
+bool differentReplacedTemplateParams(const QualType &VarType,
+ const QualType &InitializerType,
+ ASTContext &Context) {
+ if (const SubstTemplateTypeParmType *VarTmplType =
+ getSubstitutedType(VarType, Context)) {
+ if (const SubstTemplateTypeParmType *InitializerTmplType =
+ getSubstitutedType(InitializerType, Context)) {
+ return VarTmplType->getReplacedParameter()
+ ->desugar()
+ .getCanonicalType() !=
+ InitializerTmplType->getReplacedParameter()
+ ->desugar()
+ .getCanonicalType();
+ }
+ }
+ return false;
+}
+
+QualType constructorArgumentType(const VarDecl *OldVar,
+ const BoundNodes &Nodes) {
+ if (OldVar) {
+ return OldVar->getType();
+ }
+ if (const auto *FuncDecl = Nodes.getNodeAs<FunctionDecl>(FunctionDeclId)) {
+ return FuncDecl->getReturnType();
+ }
+ const auto *MethodDecl = Nodes.getNodeAs<CXXMethodDecl>(MethodDeclId);
+ return MethodDecl->getReturnType();
+}
+
} // namespace
UnnecessaryCopyInitialization::UnnecessaryCopyInitialization(
if (!CtorCall->getArg(I)->isDefaultArgument())
return;
+ // Don't apply the check if the variable and its initializer have different
+ // replaced template parameter types. In this case the check triggers for a
+ // template instantiation where the substituted types are the same, but
+ // instantiations where the types differ and rely on implicit conversion would
+ // no longer compile if we switched to a reference.
+ if (differentReplacedTemplateParams(
+ NewVar->getType(), constructorArgumentType(OldVar, Result.Nodes),
+ *Result.Context))
+ return;
+
if (OldVar == nullptr) {
handleCopyFromMethodReturn(*NewVar, *BlockStmt, *Stmt, IssueFix, ObjectArg,
*Result.Context);
Iterator<ExpensiveToCopyType> end() const;
void nonConstMethod();
bool constMethod() const;
+ template <typename A>
+ const A &templatedAccessor() const;
+ operator int() const; // Implicit conversion to int.
};
struct TrivialToCopyType {
C.constMethod();
D.constMethod();
}
+
+template <typename A>
+const A &templatedReference();
+
+template <typename A, typename B>
+void negativeTemplateTypes() {
+ A Orig;
+ // Different replaced template type params do not trigger the check. In some
+ // template instantiation this might not be a copy but an implicit
+ // conversion, so converting this to a reference might not work.
+ B AmbiguousCopy = Orig;
+ // CHECK-NOT-FIXES: B AmbiguousCopy = Orig;
+
+ B NecessaryCopy = templatedReference<A>();
+ // CHECK-NOT-FIXES: B NecessaryCopy = templatedReference<A>();
+
+ B NecessaryCopy2 = Orig.template templatedAccessor<A>();
+
+ // Non-dependent types in template still trigger the check.
+ const auto UnnecessaryCopy = ExpensiveTypeReference();
+ // CHECK-MESSAGES: [[@LINE-1]]:14: warning: the const qualified variable 'UnnecessaryCopy' is copy-constructed
+ // CHECK-FIXES: const auto& UnnecessaryCopy = ExpensiveTypeReference();
+ UnnecessaryCopy.constMethod();
+}
+
+void instantiateNegativeTemplateTypes() {
+ negativeTemplateTypes<ExpensiveToCopyType, ExpensiveToCopyType>();
+ // This template instantiation would not compile if the `AmbiguousCopy` above was made a reference.
+ negativeTemplateTypes<ExpensiveToCopyType, int>();
+}
+
+template <typename A>
+void positiveSingleTemplateType() {
+ A Orig;
+ A SingleTmplParmTypeCopy = Orig;
+ // CHECK-MESSAGES: [[@LINE-1]]:5: warning: local copy 'SingleTmplParmTypeCopy' of the variable 'Orig' is never modified
+ // CHECK-FIXES: const A& SingleTmplParmTypeCopy = Orig;
+ SingleTmplParmTypeCopy.constMethod();
+
+ A UnnecessaryCopy2 = templatedReference<A>();
+ // CHECK-MESSAGES: [[@LINE-1]]:5: warning: the variable 'UnnecessaryCopy2' is copy-constructed from a const reference
+ // CHECK-FIXES: const A& UnnecessaryCopy2 = templatedReference<A>();
+ UnnecessaryCopy2.constMethod();
+
+ A UnnecessaryCopy3 = Orig.template templatedAccessor<A>();
+ // CHECK-MESSAGES: [[@LINE-1]]:5: warning: the variable 'UnnecessaryCopy3' is copy-constructed from a const reference
+ // CHECK-FIXES: const A& UnnecessaryCopy3 = Orig.template templatedAccessor<A>();
+ UnnecessaryCopy3.constMethod();
+}
+
+void instantiatePositiveSingleTemplateType() { positiveSingleTemplateType<ExpensiveToCopyType>(); }