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/.
9 #ifndef LO_CLANG_SHARED_PLUGINS
18 (*) calls to CPPUNIT_ASSERT when it should be using CPPUNIT_ASSERT_EQUALS
19 (*) calls to CPPUNIT_ASSERT_EQUALS where the constant is the second param
24 class CppunitAssertEquals
:
25 public loplugin::FilteringPlugin
<CppunitAssertEquals
>
28 explicit CppunitAssertEquals(loplugin::InstantiationData
const & data
):
29 FilteringPlugin(data
) {}
31 virtual bool preRun() override
33 return compiler
.getLangOpts().CPlusPlus
;
36 virtual void run() override
39 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
43 bool VisitCallExpr(const CallExpr
*);
47 SourceRange range
, StringRef name
, Expr
const * expr
, bool negated
);
49 void reportEquals(SourceRange range
, StringRef name
, bool negative
);
51 bool isCompileTimeConstant(Expr
const * expr
);
54 bool CppunitAssertEquals::VisitCallExpr(const CallExpr
* callExpr
)
56 auto const decl
= callExpr
->getDirectCallee();
60 calls to CPPUNIT_ASSERT when it should be using CPPUNIT_ASSERT_EQUALS
62 if (loplugin::DeclCheck(decl
).Function("failIf").Struct("Asserter")
63 .Namespace("CppUnit").GlobalNamespace())
65 // Don't use callExpr->getLocStart() or callExpr->getExprLoc(), as those
66 // fall into a nested use of the CPPUNIT_NS macro; CallExpr::getRParenLoc
67 // happens to be readily available and cause good results:
68 auto loc
= callExpr
->getRParenLoc();
69 while (compiler
.getSourceManager().isMacroArgExpansion(loc
)) {
70 loc
= compiler
.getSourceManager().getImmediateMacroCallerLoc(loc
);
72 if (!compiler
.getSourceManager().isMacroBodyExpansion(loc
)
74 compiler
.getSourceManager().getImmediateMacroCallerLoc(loc
)))
78 auto name
= Lexer::getImmediateMacroName(
79 loc
, compiler
.getSourceManager(), compiler
.getLangOpts());
80 if (name
!= "CPPUNIT_ASSERT" && name
!= "CPPUNIT_ASSERT_MESSAGE") {
83 if (decl
->getNumParams() != 3) {
85 DiagnosticsEngine::Warning
,
86 ("TODO: suspicious CppUnit::Asserter::failIf call with %0"
88 callExpr
->getExprLoc())
89 << decl
->getNumParams() << callExpr
->getSourceRange();
92 auto const e1
= callExpr
->getArg(0)->IgnoreParenImpCasts();
93 Expr
const * e2
= nullptr;
94 if (auto const e3
= dyn_cast
<UnaryOperator
>(e1
)) {
95 if (e3
->getOpcode() == UO_LNot
) {
96 e2
= e3
->getSubExpr();
98 } else if (auto const e4
= dyn_cast
<CXXOperatorCallExpr
>(e1
)) {
99 if (e4
->getOperator() == OO_Exclaim
) {
105 DiagnosticsEngine::Warning
,
106 ("TODO: suspicious CppUnit::Asserter::failIf call not wrapping"
108 callExpr
->getExprLoc())
109 << callExpr
->getSourceRange();
112 auto range
= compat::getImmediateExpansionRange(compiler
.getSourceManager(), loc
);
114 SourceRange(range
.first
, range
.second
), name
,
115 e2
->IgnoreParenImpCasts(), false);
119 Check for calls to CPPUNIT_ASSERT_EQUALS where the constant is the second param
121 if (loplugin::DeclCheck(decl
).Function("assertEquals").
122 Namespace("CppUnit").GlobalNamespace())
124 // can happen in template test code that both params are compile time constants
125 if (isCompileTimeConstant(callExpr
->getArg(0)))
127 if (isCompileTimeConstant(callExpr
->getArg(1)))
129 DiagnosticsEngine::Warning
,
130 "CPPUNIT_ASSERT_EQUALS parameters look switched, expected value should be first param",
131 callExpr
->getExprLoc())
132 << callExpr
->getSourceRange();
137 // copied from stringconcat.cxx
138 Expr
const * stripConstructor(Expr
const * expr
) {
140 auto const e1
= dyn_cast
<CXXFunctionalCastExpr
>(e0
);
142 e0
= e1
->getSubExpr()->IgnoreParenImpCasts();
144 auto const e2
= dyn_cast
<CXXBindTemporaryExpr
>(e0
);
148 auto const e3
= dyn_cast
<CXXConstructExpr
>(
149 e2
->getSubExpr()->IgnoreParenImpCasts());
153 auto qt
= loplugin::DeclCheck(e3
->getConstructor());
154 if (!((qt
.MemberFunction().Class("OString").Namespace("rtl")
156 || (qt
.MemberFunction().Class("OUString").Namespace("rtl")
157 .GlobalNamespace())))
161 if (e3
->getNumArgs() != 2) {
164 return e3
->getArg(0)->IgnoreParenImpCasts();
167 bool CppunitAssertEquals::isCompileTimeConstant(Expr
const * expr
)
169 if (expr
->isIntegerConstantExpr(compiler
.getASTContext()))
171 // is string literal ?
172 expr
= expr
->IgnoreParenImpCasts();
173 expr
= stripConstructor(expr
);
174 return isa
<clang::StringLiteral
>(expr
);
177 void CppunitAssertEquals::checkExpr(
178 SourceRange range
, StringRef name
, Expr
const * expr
, bool negated
)
180 if (auto const e
= dyn_cast
<UnaryOperator
>(expr
)) {
181 if (e
->getOpcode() == UO_LNot
) {
183 range
, name
, e
->getSubExpr()->IgnoreParenImpCasts(), !negated
);
187 if (auto const e
= dyn_cast
<BinaryOperator
>(expr
)) {
188 auto const op
= e
->getOpcode();
189 if ((!negated
&& op
== BO_EQ
) || (negated
&& op
== BO_NE
)) {
190 reportEquals(range
, name
, op
== BO_NE
);
193 #if 0 // TODO: enable later
194 if ((!negated
&& op
== BO_LAnd
) || (negated
&& op
== BO_LOr
)) {
196 DiagnosticsEngine::Warning
,
197 "rather split into two %0", e
->getExprLoc())
204 if (auto const e
= dyn_cast
<CXXOperatorCallExpr
>(expr
)) {
205 auto const op
= e
->getOperator();
206 if ((!negated
&& op
== OO_EqualEqual
)
207 || (negated
&& op
== OO_ExclaimEqual
))
209 reportEquals(range
, name
, op
== OO_ExclaimEqual
);
216 void CppunitAssertEquals::reportEquals(
217 SourceRange range
, StringRef name
, bool negative
)
220 DiagnosticsEngine::Warning
,
222 " %select{CPPUNIT_ASSERT_EQUAL|CPPUNIT_ASSERT_EQUAL_MESSAGE}0 (or"
223 " rewrite as an explicit operator %select{==|!=}1 call when the"
224 " operator itself is the topic)"),
226 << (name
== "CPPUNIT_ASSERT_MESSAGE") << negative
<< range
;
229 loplugin::Plugin::Registration
< CppunitAssertEquals
> cppunitassertequals("cppunitassertequals");
233 #endif // LO_CLANG_SHARED_PLUGINS
235 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */