1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
17 (*) calls to CPPUNIT_ASSERT when it should be using CPPUNIT_ASSERT_EQUALS
18 (*) calls to CPPUNIT_ASSERT_EQUALS where the constant is the second param
23 class CppunitAssertEquals
:
24 public loplugin::FilteringPlugin
<CppunitAssertEquals
>
27 explicit CppunitAssertEquals(loplugin::InstantiationData
const & data
):
28 FilteringPlugin(data
) {}
30 virtual void run() override
32 if (compiler
.getLangOpts().CPlusPlus
) {
33 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
37 bool VisitCallExpr(const CallExpr
*);
41 SourceRange range
, StringRef name
, Expr
const * expr
, bool negated
);
43 void reportEquals(SourceRange range
, StringRef name
, bool negative
);
45 bool isCompileTimeConstant(Expr
const * expr
);
48 bool CppunitAssertEquals::VisitCallExpr(const CallExpr
* callExpr
)
50 auto const decl
= callExpr
->getDirectCallee();
54 calls to CPPUNIT_ASSERT when it should be using CPPUNIT_ASSERT_EQUALS
56 if (loplugin::DeclCheck(decl
).Function("failIf").Struct("Asserter")
57 .Namespace("CppUnit").GlobalNamespace())
59 // Don't use callExpr->getLocStart() or callExpr->getExprLoc(), as those
60 // fall into a nested use of the CPPUNIT_NS macro; CallExpr::getRParenLoc
61 // happens to be readily available and cause good results:
62 auto loc
= callExpr
->getRParenLoc();
63 while (compiler
.getSourceManager().isMacroArgExpansion(loc
)) {
64 loc
= compiler
.getSourceManager().getImmediateMacroCallerLoc(loc
);
66 if (!compiler
.getSourceManager().isMacroBodyExpansion(loc
)
68 compiler
.getSourceManager().getImmediateMacroCallerLoc(loc
)))
72 auto name
= Lexer::getImmediateMacroName(
73 loc
, compiler
.getSourceManager(), compiler
.getLangOpts());
74 if (name
!= "CPPUNIT_ASSERT" && name
!= "CPPUNIT_ASSERT_MESSAGE") {
77 if (decl
->getNumParams() != 3) {
79 DiagnosticsEngine::Warning
,
80 ("TODO: suspicious CppUnit::Asserter::failIf call with %0"
82 callExpr
->getExprLoc())
83 << decl
->getNumParams() << callExpr
->getSourceRange();
86 auto const e1
= callExpr
->getArg(0)->IgnoreParenImpCasts();
87 Expr
const * e2
= nullptr;
88 if (auto const e3
= dyn_cast
<UnaryOperator
>(e1
)) {
89 if (e3
->getOpcode() == UO_LNot
) {
90 e2
= e3
->getSubExpr();
92 } else if (auto const e4
= dyn_cast
<CXXOperatorCallExpr
>(e1
)) {
93 if (e4
->getOperator() == OO_Exclaim
) {
99 DiagnosticsEngine::Warning
,
100 ("TODO: suspicious CppUnit::Asserter::failIf call not wrapping"
102 callExpr
->getExprLoc())
103 << callExpr
->getSourceRange();
106 auto range
= compat::getImmediateExpansionRange(compiler
.getSourceManager(), loc
);
108 SourceRange(range
.first
, range
.second
), name
,
109 e2
->IgnoreParenImpCasts(), false);
113 Check for calls to CPPUNIT_ASSERT_EQUALS where the constant is the second param
115 if (loplugin::DeclCheck(decl
).Function("assertEquals").
116 Namespace("CppUnit").GlobalNamespace())
118 // can happen in template test code that both params are compile time constants
119 if (isCompileTimeConstant(callExpr
->getArg(0)))
121 if (isCompileTimeConstant(callExpr
->getArg(1)))
123 DiagnosticsEngine::Warning
,
124 "CPPUNIT_ASSERT_EQUALS parameters look switched, expected value should be first param",
125 callExpr
->getExprLoc())
126 << callExpr
->getSourceRange();
131 // copied from stringconcat.cxx
132 Expr
const * stripCtor(Expr
const * expr
) {
134 auto const e1
= dyn_cast
<CXXFunctionalCastExpr
>(e0
);
136 e0
= e1
->getSubExpr()->IgnoreParenImpCasts();
138 auto const e2
= dyn_cast
<CXXBindTemporaryExpr
>(e0
);
142 auto const e3
= dyn_cast
<CXXConstructExpr
>(
143 e2
->getSubExpr()->IgnoreParenImpCasts());
147 auto qt
= loplugin::DeclCheck(e3
->getConstructor());
148 if (!((qt
.MemberFunction().Class("OString").Namespace("rtl")
150 || (qt
.MemberFunction().Class("OUString").Namespace("rtl")
151 .GlobalNamespace())))
155 if (e3
->getNumArgs() != 2) {
158 return e3
->getArg(0)->IgnoreParenImpCasts();
161 bool CppunitAssertEquals::isCompileTimeConstant(Expr
const * expr
)
163 if (expr
->isIntegerConstantExpr(compiler
.getASTContext()))
165 // is string literal ?
166 expr
= expr
->IgnoreParenImpCasts();
167 expr
= stripCtor(expr
);
168 return isa
<clang::StringLiteral
>(expr
);
171 void CppunitAssertEquals::checkExpr(
172 SourceRange range
, StringRef name
, Expr
const * expr
, bool negated
)
174 if (auto const e
= dyn_cast
<UnaryOperator
>(expr
)) {
175 if (e
->getOpcode() == UO_LNot
) {
177 range
, name
, e
->getSubExpr()->IgnoreParenImpCasts(), !negated
);
181 if (auto const e
= dyn_cast
<BinaryOperator
>(expr
)) {
182 auto const op
= e
->getOpcode();
183 if ((!negated
&& op
== BO_EQ
) || (negated
&& op
== BO_NE
)) {
184 reportEquals(range
, name
, op
== BO_NE
);
187 #if 0 // TODO: enable later
188 if ((!negated
&& op
== BO_LAnd
) || (negated
&& op
== BO_LOr
)) {
190 DiagnosticsEngine::Warning
,
191 "rather split into two %0", e
->getExprLoc())
198 if (auto const e
= dyn_cast
<CXXOperatorCallExpr
>(expr
)) {
199 auto const op
= e
->getOperator();
200 if ((!negated
&& op
== OO_EqualEqual
)
201 || (negated
&& op
== OO_ExclaimEqual
))
203 reportEquals(range
, name
, op
== OO_ExclaimEqual
);
210 void CppunitAssertEquals::reportEquals(
211 SourceRange range
, StringRef name
, bool negative
)
214 DiagnosticsEngine::Warning
,
216 " %select{CPPUNIT_ASSERT_EQUAL|CPPUNIT_ASSERT_EQUAL_MESSAGE}0 (or"
217 " rewrite as an explicit operator %select{==|!=}1 call when the"
218 " operator itself is the topic)"),
220 << (name
== "CPPUNIT_ASSERT_MESSAGE") << negative
<< range
;
223 loplugin::Plugin::Registration
< CppunitAssertEquals
> X("cppunitassertequals");
227 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */