Index: include/clang/AST/Expr.h =================================================================== --- include/clang/AST/Expr.h +++ include/clang/AST/Expr.h @@ -3398,6 +3398,9 @@ static bool isComparisonOp(Opcode Opc) { return Opc >= BO_Cmp && Opc<=BO_NE; } bool isComparisonOp() const { return isComparisonOp(getOpcode()); } + static bool isCommaOp(Opcode Opc) { return Opc == BO_Comma; } + bool isCommaOp() const { return isCommaOp(getOpcode()); } + static Opcode negateComparisonOp(Opcode Opc) { switch (Opc) { default: Index: lib/Analysis/ExprMutationAnalyzer.cpp =================================================================== --- lib/Analysis/ExprMutationAnalyzer.cpp +++ lib/Analysis/ExprMutationAnalyzer.cpp @@ -24,6 +24,18 @@ return InnerMatcher.matches(*Range, Finder, Builder); } +AST_MATCHER_P(Expr, skipCommaOps, + ast_matchers::internal::Matcher, InnerMatcher) { + const Expr* Result = &Node; + while (const auto *BOComma = + dyn_cast_or_null(Result->IgnoreParens())) { + if (!BOComma->isCommaOp()) + break; + Result = BOComma->getRHS(); + } + return InnerMatcher.matches(*Result, Finder, Builder); +} + const ast_matchers::internal::VariadicDynCastAllOfMatcher cxxTypeidExpr; @@ -193,18 +205,20 @@ const Stmt *ExprMutationAnalyzer::findDirectMutation(const Expr *Exp) { // LHS of any assignment operators. const auto AsAssignmentLhs = - binaryOperator(isAssignmentOperator(), hasLHS(equalsNode(Exp))); + binaryOperator(isAssignmentOperator(), + hasLHS(skipCommaOps(equalsNode(Exp)))); // Operand of increment/decrement operators. const auto AsIncDecOperand = unaryOperator(anyOf(hasOperatorName("++"), hasOperatorName("--")), - hasUnaryOperand(equalsNode(Exp))); + hasUnaryOperand(skipCommaOps(equalsNode(Exp)))); // Invoking non-const member function. // A member function is assumed to be non-const when it is unresolved. const auto NonConstMethod = cxxMethodDecl(unless(isConst())); const auto AsNonConstThis = - expr(anyOf(cxxMemberCallExpr(callee(NonConstMethod), on(equalsNode(Exp))), + expr(anyOf(cxxMemberCallExpr(callee(NonConstMethod), + on(skipCommaOps(equalsNode(Exp)))), cxxOperatorCallExpr(callee(NonConstMethod), hasArgument(0, equalsNode(Exp))), callExpr(callee(expr(anyOf( @@ -220,7 +234,7 @@ unaryOperator(hasOperatorName("&"), // A NoOp implicit cast is adding const. unless(hasParent(implicitCastExpr(hasCastKind(CK_NoOp)))), - hasUnaryOperand(equalsNode(Exp))); + hasUnaryOperand(skipCommaOps(equalsNode(Exp)))); const auto AsPointerFromArrayDecay = castExpr(hasCastKind(CK_ArrayToPointerDecay), unless(hasParent(arraySubscriptExpr())), has(equalsNode(Exp))); @@ -239,7 +253,8 @@ // findFunctionArgMutation which has additional smarts for handling forwarding // references. const auto NonConstRefParam = forEachArgumentWithParam( - equalsNode(Exp), parmVarDecl(hasType(nonConstReferenceType()))); + skipCommaOps(equalsNode(Exp)), + parmVarDecl(hasType(nonConstReferenceType()))); const auto NotInstantiated = unless(hasDeclaration(isInstantiated())); const auto AsNonConstRefArg = anyOf( callExpr(NonConstRefParam, NotInstantiated), Index: unittests/Analysis/ExprMutationAnalyzerTest.cpp =================================================================== --- unittests/Analysis/ExprMutationAnalyzerTest.cpp +++ unittests/Analysis/ExprMutationAnalyzerTest.cpp @@ -881,6 +881,65 @@ EXPECT_FALSE(isMutated(Results, AST.get())); } +TEST(ExprMutationAnalyzerTest, CommaExprWithAnAssigment) { + const auto AST = + buildASTFromCodeWithArgs("void f() { int x; int y; (x, y) = 5; }", + {"-Wno-unused-value"}); + const auto Results = + match(withEnclosingCompound(declRefTo("y")), AST->getASTContext()); + EXPECT_TRUE(isMutated(Results, AST.get())); +} + +TEST(ExprMutationAnalyzerTest, CommaExprWithDecOp) { + const auto AST = + buildASTFromCodeWithArgs("void f() { int x; int y; (x, y)++; }", + {"-Wno-unused-value"}); + const auto Results = + match(withEnclosingCompound(declRefTo("y")), AST->getASTContext()); + EXPECT_TRUE(isMutated(Results, AST.get())); +} + +TEST(ExprMutationAnalyzerTest, CommaExprWithNonConstMemberCall) { + const auto AST = + buildASTFromCodeWithArgs("class A { public: int mem; void f() { mem ++; } };" + "void fn() { A o1, o2; (o1, o2).f(); }", + {"-Wno-unused-value"}); + const auto Results = + match(withEnclosingCompound(declRefTo("o2")), AST->getASTContext()); + EXPECT_TRUE(isMutated(Results, AST.get())); +} + +TEST(ExprMutationAnalyzerTest, CommaExprWithConstMemberCall) { + const auto AST = + buildASTFromCodeWithArgs("class A { public: int mem; void f() const { } };" + "void fn() { A o1, o2; (o1, o2).f(); }", + {"-Wno-unused-value"}); + const auto Results = + match(withEnclosingCompound(declRefTo("o2")), AST->getASTContext()); + EXPECT_FALSE(isMutated(Results, AST.get())); +} + +TEST(ExprMutationAnalyzerTest, CommaExprParmRef) { + const auto AST = + buildASTFromCodeWithArgs("class A { public: int mem;};" + "extern void fn(A &o1);" + "void fn2 () { A o1, o2; fn((o2, o1)); } ", + {"-Wno-unused-value"}); + const auto Results = + match(withEnclosingCompound(declRefTo("o1")), AST->getASTContext()); + EXPECT_TRUE(isMutated(Results, AST.get())); +} + +TEST(ExprMutationAnalyzerTest, CommaExprWithAmpersandOp) { + const auto AST = + buildASTFromCodeWithArgs("class A { public: int mem;};" + "void fn () { A o1, o2; void *addr = &(o2, o1); } ", + {"-Wno-unused-value"}); + const auto Results = + match(withEnclosingCompound(declRefTo("o1")), AST->getASTContext()); + EXPECT_TRUE(isMutated(Results, AST.get())); +} + TEST(ExprMutationAnalyzerTest, LambdaDefaultCaptureByValue) { const auto AST = buildASTFromCode("void f() { int x; [=]() { x; }; }"); const auto Results =