Skip to content

Commit eb39991

Browse files
[analyzer] handle modification of vars inside an expr with comma operator
We should track mutation of a variable within a comma operator expression. Current code in ExprMutationAnalyzer does not handle it. This will handle cases like: (a, b) ++ < == b is modified (a, b) = c < == b is modifed Patch by Djordje Todorovic. Differential Revision: https://reviews.llvm.org/D58894 llvm-svn: 355605
1 parent a927114 commit eb39991

File tree

3 files changed

+167
-13
lines changed

3 files changed

+167
-13
lines changed

clang/include/clang/AST/Expr.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3403,6 +3403,9 @@ class BinaryOperator : public Expr {
34033403
static bool isComparisonOp(Opcode Opc) { return Opc >= BO_Cmp && Opc<=BO_NE; }
34043404
bool isComparisonOp() const { return isComparisonOp(getOpcode()); }
34053405

3406+
static bool isCommaOp(Opcode Opc) { return Opc == BO_Comma; }
3407+
bool isCommaOp() const { return isCommaOp(getOpcode()); }
3408+
34063409
static Opcode negateComparisonOp(Opcode Opc) {
34073410
switch (Opc) {
34083411
default:

clang/lib/Analysis/ExprMutationAnalyzer.cpp

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@ AST_MATCHER_P(CXXForRangeStmt, hasRangeStmt,
2424
return InnerMatcher.matches(*Range, Finder, Builder);
2525
}
2626

27+
AST_MATCHER_P(Expr, maybeEvalCommaExpr,
28+
ast_matchers::internal::Matcher<Expr>, InnerMatcher) {
29+
const Expr* Result = &Node;
30+
while (const auto *BOComma =
31+
dyn_cast_or_null<BinaryOperator>(Result->IgnoreParens())) {
32+
if (!BOComma->isCommaOp())
33+
break;
34+
Result = BOComma->getRHS();
35+
}
36+
return InnerMatcher.matches(*Result, Finder, Builder);
37+
}
38+
2739
const ast_matchers::internal::VariadicDynCastAllOfMatcher<Stmt, CXXTypeidExpr>
2840
cxxTypeidExpr;
2941

@@ -193,24 +205,28 @@ const Stmt *ExprMutationAnalyzer::findDeclPointeeMutation(
193205
const Stmt *ExprMutationAnalyzer::findDirectMutation(const Expr *Exp) {
194206
// LHS of any assignment operators.
195207
const auto AsAssignmentLhs =
196-
binaryOperator(isAssignmentOperator(), hasLHS(equalsNode(Exp)));
208+
binaryOperator(isAssignmentOperator(),
209+
hasLHS(maybeEvalCommaExpr(equalsNode(Exp))));
197210

198211
// Operand of increment/decrement operators.
199212
const auto AsIncDecOperand =
200213
unaryOperator(anyOf(hasOperatorName("++"), hasOperatorName("--")),
201-
hasUnaryOperand(equalsNode(Exp)));
214+
hasUnaryOperand(maybeEvalCommaExpr(equalsNode(Exp))));
202215

203216
// Invoking non-const member function.
204217
// A member function is assumed to be non-const when it is unresolved.
205218
const auto NonConstMethod = cxxMethodDecl(unless(isConst()));
206219
const auto AsNonConstThis =
207-
expr(anyOf(cxxMemberCallExpr(callee(NonConstMethod), on(equalsNode(Exp))),
220+
expr(anyOf(cxxMemberCallExpr(callee(NonConstMethod),
221+
on(maybeEvalCommaExpr(equalsNode(Exp)))),
208222
cxxOperatorCallExpr(callee(NonConstMethod),
209-
hasArgument(0, equalsNode(Exp))),
223+
hasArgument(0,
224+
maybeEvalCommaExpr(equalsNode(Exp)))),
210225
callExpr(callee(expr(anyOf(
211-
unresolvedMemberExpr(hasObjectExpression(equalsNode(Exp))),
226+
unresolvedMemberExpr(
227+
hasObjectExpression(maybeEvalCommaExpr(equalsNode(Exp)))),
212228
cxxDependentScopeMemberExpr(
213-
hasObjectExpression(equalsNode(Exp)))))))));
229+
hasObjectExpression(maybeEvalCommaExpr(equalsNode(Exp))))))))));
214230

215231
// Taking address of 'Exp'.
216232
// We're assuming 'Exp' is mutated as soon as its address is taken, though in
@@ -220,35 +236,38 @@ const Stmt *ExprMutationAnalyzer::findDirectMutation(const Expr *Exp) {
220236
unaryOperator(hasOperatorName("&"),
221237
// A NoOp implicit cast is adding const.
222238
unless(hasParent(implicitCastExpr(hasCastKind(CK_NoOp)))),
223-
hasUnaryOperand(equalsNode(Exp)));
239+
hasUnaryOperand(maybeEvalCommaExpr(equalsNode(Exp))));
224240
const auto AsPointerFromArrayDecay =
225241
castExpr(hasCastKind(CK_ArrayToPointerDecay),
226-
unless(hasParent(arraySubscriptExpr())), has(equalsNode(Exp)));
242+
unless(hasParent(arraySubscriptExpr())),
243+
has(maybeEvalCommaExpr(equalsNode(Exp))));
227244
// Treat calling `operator->()` of move-only classes as taking address.
228245
// These are typically smart pointers with unique ownership so we treat
229246
// mutation of pointee as mutation of the smart pointer itself.
230247
const auto AsOperatorArrowThis =
231248
cxxOperatorCallExpr(hasOverloadedOperatorName("->"),
232249
callee(cxxMethodDecl(ofClass(isMoveOnly()),
233250
returns(nonConstPointerType()))),
234-
argumentCountIs(1), hasArgument(0, equalsNode(Exp)));
251+
argumentCountIs(1),
252+
hasArgument(0, maybeEvalCommaExpr(equalsNode(Exp))));
235253

236254
// Used as non-const-ref argument when calling a function.
237255
// An argument is assumed to be non-const-ref when the function is unresolved.
238256
// Instantiated template functions are not handled here but in
239257
// findFunctionArgMutation which has additional smarts for handling forwarding
240258
// references.
241259
const auto NonConstRefParam = forEachArgumentWithParam(
242-
equalsNode(Exp), parmVarDecl(hasType(nonConstReferenceType())));
260+
maybeEvalCommaExpr(equalsNode(Exp)),
261+
parmVarDecl(hasType(nonConstReferenceType())));
243262
const auto NotInstantiated = unless(hasDeclaration(isInstantiated()));
244263
const auto AsNonConstRefArg = anyOf(
245264
callExpr(NonConstRefParam, NotInstantiated),
246265
cxxConstructExpr(NonConstRefParam, NotInstantiated),
247266
callExpr(callee(expr(anyOf(unresolvedLookupExpr(), unresolvedMemberExpr(),
248267
cxxDependentScopeMemberExpr(),
249268
hasType(templateTypeParmType())))),
250-
hasAnyArgument(equalsNode(Exp))),
251-
cxxUnresolvedConstructExpr(hasAnyArgument(equalsNode(Exp))));
269+
hasAnyArgument(maybeEvalCommaExpr(equalsNode(Exp)))),
270+
cxxUnresolvedConstructExpr(hasAnyArgument(maybeEvalCommaExpr(equalsNode(Exp)))));
252271

253272
// Captured by a lambda by reference.
254273
// If we're initializing a capture with 'Exp' directly then we're initializing
@@ -261,7 +280,8 @@ const Stmt *ExprMutationAnalyzer::findDirectMutation(const Expr *Exp) {
261280
// For returning by value there will be an ImplicitCastExpr <LValueToRValue>.
262281
// For returning by const-ref there will be an ImplicitCastExpr <NoOp> (for
263282
// adding const.)
264-
const auto AsNonConstRefReturn = returnStmt(hasReturnValue(equalsNode(Exp)));
283+
const auto AsNonConstRefReturn = returnStmt(hasReturnValue(
284+
maybeEvalCommaExpr(equalsNode(Exp))));
265285

266286
const auto Matches =
267287
match(findAll(stmt(anyOf(AsAssignmentLhs, AsIncDecOperand, AsNonConstThis,

clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -881,6 +881,137 @@ TEST(ExprMutationAnalyzerTest, CastToConstRef) {
881881
EXPECT_FALSE(isMutated(Results, AST.get()));
882882
}
883883

884+
TEST(ExprMutationAnalyzerTest, CommaExprWithAnAssigment) {
885+
const auto AST =
886+
buildASTFromCodeWithArgs("void f() { int x; int y; (x, y) = 5; }",
887+
{"-Wno-unused-value"});
888+
const auto Results =
889+
match(withEnclosingCompound(declRefTo("y")), AST->getASTContext());
890+
EXPECT_TRUE(isMutated(Results, AST.get()));
891+
}
892+
893+
TEST(ExprMutationAnalyzerTest, CommaExprWithDecOp) {
894+
const auto AST =
895+
buildASTFromCodeWithArgs("void f() { int x; int y; (x, y)++; }",
896+
{"-Wno-unused-value"});
897+
const auto Results =
898+
match(withEnclosingCompound(declRefTo("y")), AST->getASTContext());
899+
EXPECT_TRUE(isMutated(Results, AST.get()));
900+
}
901+
902+
TEST(ExprMutationAnalyzerTest, CommaExprWithNonConstMemberCall) {
903+
const auto AST =
904+
buildASTFromCodeWithArgs("class A { public: int mem; void f() { mem ++; } };"
905+
"void fn() { A o1, o2; (o1, o2).f(); }",
906+
{"-Wno-unused-value"});
907+
const auto Results =
908+
match(withEnclosingCompound(declRefTo("o2")), AST->getASTContext());
909+
EXPECT_TRUE(isMutated(Results, AST.get()));
910+
}
911+
912+
TEST(ExprMutationAnalyzerTest, CommaExprWithConstMemberCall) {
913+
const auto AST =
914+
buildASTFromCodeWithArgs("class A { public: int mem; void f() const { } };"
915+
"void fn() { A o1, o2; (o1, o2).f(); }",
916+
{"-Wno-unused-value"});
917+
const auto Results =
918+
match(withEnclosingCompound(declRefTo("o2")), AST->getASTContext());
919+
EXPECT_FALSE(isMutated(Results, AST.get()));
920+
}
921+
922+
TEST(ExprMutationAnalyzerTest, CommaExprWithCallExpr) {
923+
const auto AST =
924+
buildASTFromCodeWithArgs("class A { public: int mem; void f(A &O1) {} };"
925+
"void fn() { A o1, o2; o2.f((o2, o1)); }",
926+
{"-Wno-unused-value"});
927+
const auto Results =
928+
match(withEnclosingCompound(declRefTo("o1")), AST->getASTContext());
929+
EXPECT_TRUE(isMutated(Results, AST.get()));
930+
}
931+
932+
TEST(ExprMutationAnalyzerTest, CommaExprWithCallUnresolved) {
933+
auto AST = buildASTFromCodeWithArgs(
934+
"template <class T> struct S;"
935+
"template <class T> void f() { S<T> s; int x, y; s.mf((y, x)); }",
936+
{"-fno-delayed-template-parsing -Wno-unused-value"});
937+
auto Results =
938+
match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
939+
EXPECT_TRUE(isMutated(Results, AST.get()));
940+
941+
AST = buildASTFromCodeWithArgs(
942+
"template <class T> void f(T t) { int x, y; g(t, (y, x)); }",
943+
{"-fno-delayed-template-parsing -Wno-unused-value"});
944+
Results = match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
945+
EXPECT_TRUE(isMutated(Results, AST.get()));
946+
}
947+
948+
TEST(ExprMutationAnalyzerTest, CommaExprParmRef) {
949+
const auto AST =
950+
buildASTFromCodeWithArgs("class A { public: int mem;};"
951+
"extern void fn(A &o1);"
952+
"void fn2 () { A o1, o2; fn((o2, o1)); } ",
953+
{"-Wno-unused-value"});
954+
const auto Results =
955+
match(withEnclosingCompound(declRefTo("o1")), AST->getASTContext());
956+
EXPECT_TRUE(isMutated(Results, AST.get()));
957+
}
958+
959+
TEST(ExprMutationAnalyzerTest, CommaExprWithAmpersandOp) {
960+
const auto AST =
961+
buildASTFromCodeWithArgs("class A { public: int mem;};"
962+
"void fn () { A o1, o2;"
963+
"void *addr = &(o2, o1); } ",
964+
{"-Wno-unused-value"});
965+
const auto Results =
966+
match(withEnclosingCompound(declRefTo("o1")), AST->getASTContext());
967+
EXPECT_TRUE(isMutated(Results, AST.get()));
968+
}
969+
970+
TEST(ExprMutationAnalyzerTest, CommaExprAsReturnAsValue) {
971+
auto AST = buildASTFromCodeWithArgs("int f() { int x, y; return (x, y); }",
972+
{"-Wno-unused-value"});
973+
auto Results =
974+
match(withEnclosingCompound(declRefTo("y")), AST->getASTContext());
975+
EXPECT_FALSE(isMutated(Results, AST.get()));
976+
}
977+
978+
TEST(ExprMutationAnalyzerTest, CommaEpxrAsReturnAsNonConstRef) {
979+
const auto AST =
980+
buildASTFromCodeWithArgs("int& f() { int x, y; return (y, x); }",
981+
{"-Wno-unused-value"});
982+
const auto Results =
983+
match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
984+
EXPECT_TRUE(isMutated(Results, AST.get()));
985+
}
986+
987+
TEST(ExprMutationAnalyzerTest, CommaExprAsArrayToPointerDecay) {
988+
const auto AST =
989+
buildASTFromCodeWithArgs("void g(int*); "
990+
"void f() { int x[2], y[2]; g((y, x)); }",
991+
{"-Wno-unused-value"});
992+
const auto Results =
993+
match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
994+
EXPECT_TRUE(isMutated(Results, AST.get()));
995+
}
996+
997+
TEST(ExprMutationAnalyzerTest, CommaExprAsUniquePtr) {
998+
const std::string UniquePtrDef =
999+
"template <class T> struct UniquePtr {"
1000+
" UniquePtr();"
1001+
" UniquePtr(const UniquePtr&) = delete;"
1002+
" T& operator*() const;"
1003+
" T* operator->() const;"
1004+
"};";
1005+
const auto AST = buildASTFromCodeWithArgs(
1006+
UniquePtrDef + "template <class T> void f() "
1007+
"{ UniquePtr<T> x; UniquePtr<T> y;"
1008+
" (y, x)->mf(); }",
1009+
{"-fno-delayed-template-parsing -Wno-unused-value"});
1010+
const auto Results =
1011+
match(withEnclosingCompound(declRefTo("x")), AST->getASTContext());
1012+
EXPECT_TRUE(isMutated(Results, AST.get()));
1013+
}
1014+
8841015
TEST(ExprMutationAnalyzerTest, LambdaDefaultCaptureByValue) {
8851016
const auto AST = buildASTFromCode("void f() { int x; [=]() { x; }; }");
8861017
const auto Results =

0 commit comments

Comments
 (0)