[analyzer] Handle forwarding reference better in ExprMutationAnalyzer.
Summary:
We used to treat an `Expr` mutated whenever it's passed as non-const
reference argument to a function. This results in false positives in
cases like this:
```
int x;
std::vector<int> v;
v.emplace_back(x); // `x` is passed as non-const reference to `emplace_back`
```
In theory the false positives can be suppressed with
`v.emplace_back(std::as_const(x))` but that's considered overly verbose,
inconsistent with existing code and spammy as diags.
This diff handles such cases by following into the function definition
and see whether the argument is mutated inside.
Reviewers: lebedev.ri, JonasToth, george.karpenkov
Subscribers: xazax.hun, szepet, a.sidorin, mikhail.ramalho, Szelethus, cfe-commits
Differential Revision: https://reviews.llvm.org/D52008
llvm-svn: 342271
diff --git a/clang/lib/Analysis/ExprMutationAnalyzer.cpp b/clang/lib/Analysis/ExprMutationAnalyzer.cpp
index f4be35b..678eeeb 100644
--- a/clang/lib/Analysis/ExprMutationAnalyzer.cpp
+++ b/clang/lib/Analysis/ExprMutationAnalyzer.cpp
@@ -79,7 +79,8 @@
&ExprMutationAnalyzer::findArrayElementMutation,
&ExprMutationAnalyzer::findCastMutation,
&ExprMutationAnalyzer::findRangeLoopMutation,
- &ExprMutationAnalyzer::findReferenceMutation}) {
+ &ExprMutationAnalyzer::findReferenceMutation,
+ &ExprMutationAnalyzer::findFunctionArgMutation}) {
if (const Stmt *S = (this->*Finder)(Exp))
return Results[Exp] = S;
}
@@ -192,10 +193,15 @@
// Used as non-const-ref argument when calling a function.
// An argument is assumed to be non-const-ref when the function is unresolved.
+ // Instantiated template functions are not handled here but in
+ // findFunctionArgMutation which has additional smarts for handling forwarding
+ // references.
const auto NonConstRefParam = forEachArgumentWithParam(
equalsNode(Exp), parmVarDecl(hasType(nonConstReferenceType())));
+ const auto NotInstantiated = unless(hasDeclaration(isInstantiated()));
const auto AsNonConstRefArg = anyOf(
- callExpr(NonConstRefParam), cxxConstructExpr(NonConstRefParam),
+ callExpr(NonConstRefParam, NotInstantiated),
+ cxxConstructExpr(NonConstRefParam, NotInstantiated),
callExpr(callee(expr(anyOf(unresolvedLookupExpr(), unresolvedMemberExpr(),
cxxDependentScopeMemberExpr(),
hasType(templateTypeParmType())))),
@@ -305,4 +311,82 @@
return findDeclMutation(Refs);
}
+const Stmt *ExprMutationAnalyzer::findFunctionArgMutation(const Expr *Exp) {
+ const auto NonConstRefParam = forEachArgumentWithParam(
+ equalsNode(Exp),
+ parmVarDecl(hasType(nonConstReferenceType())).bind("parm"));
+ const auto IsInstantiated = hasDeclaration(isInstantiated());
+ const auto FuncDecl = hasDeclaration(functionDecl().bind("func"));
+ const auto Matches = match(
+ findAll(expr(anyOf(callExpr(NonConstRefParam, IsInstantiated, FuncDecl),
+ cxxConstructExpr(NonConstRefParam, IsInstantiated,
+ FuncDecl)))
+ .bind("expr")),
+ Stm, Context);
+ for (const auto &Nodes : Matches) {
+ const auto *Exp = Nodes.getNodeAs<Expr>("expr");
+ const auto *Func = Nodes.getNodeAs<FunctionDecl>("func");
+ if (!Func->getBody())
+ return Exp;
+
+ const auto *Parm = Nodes.getNodeAs<ParmVarDecl>("parm");
+ const ArrayRef<ParmVarDecl *> AllParams =
+ Func->getPrimaryTemplate()->getTemplatedDecl()->parameters();
+ QualType ParmType =
+ AllParams[std::min<size_t>(Parm->getFunctionScopeIndex(),
+ AllParams.size() - 1)]
+ ->getType();
+ if (const auto *T = ParmType->getAs<PackExpansionType>())
+ ParmType = T->getPattern();
+
+ // If param type is forwarding reference, follow into the function
+ // definition and see whether the param is mutated inside.
+ if (const auto *RefType = ParmType->getAs<RValueReferenceType>()) {
+ if (!RefType->getPointeeType().getQualifiers() &&
+ RefType->getPointeeType()->getAs<TemplateTypeParmType>()) {
+ std::unique_ptr<FunctionParmMutationAnalyzer> &Analyzer =
+ FuncParmAnalyzer[Func];
+ if (!Analyzer)
+ Analyzer.reset(new FunctionParmMutationAnalyzer(*Func, Context));
+ if (Analyzer->findMutation(Parm))
+ return Exp;
+ continue;
+ }
+ }
+ // Not forwarding reference.
+ return Exp;
+ }
+ return nullptr;
+}
+
+FunctionParmMutationAnalyzer::FunctionParmMutationAnalyzer(
+ const FunctionDecl &Func, ASTContext &Context)
+ : BodyAnalyzer(*Func.getBody(), Context) {
+ if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(&Func)) {
+ // CXXCtorInitializer might also mutate Param but they're not part of
+ // function body, check them eagerly here since they're typically trivial.
+ for (const CXXCtorInitializer *Init : Ctor->inits()) {
+ ExprMutationAnalyzer InitAnalyzer(*Init->getInit(), Context);
+ for (const ParmVarDecl *Parm : Ctor->parameters()) {
+ if (Results.find(Parm) != Results.end())
+ continue;
+ if (const Stmt *S = InitAnalyzer.findDeclMutation(Parm))
+ Results[Parm] = S;
+ }
+ }
+ }
+}
+
+const Stmt *
+FunctionParmMutationAnalyzer::findMutation(const ParmVarDecl *Parm) {
+ const auto Memoized = Results.find(Parm);
+ if (Memoized != Results.end())
+ return Memoized->second;
+
+ if (const Stmt *S = BodyAnalyzer.findDeclMutation(Parm))
+ return Results[Parm] = S;
+
+ return Results[Parm] = nullptr;
+}
+
} // namespace clang