bump product version to 6.4.0.3
[LibreOffice.git] / compilerplugins / clang / cppunitassertequals.cxx
blob930ecdd74e13f04e7d840b443377317eb94c017f
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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/.
8 */
9 #ifndef LO_CLANG_SHARED_PLUGINS
11 #include "plugin.hxx"
12 #include "check.hxx"
13 #include "compat.hxx"
14 #include <iostream>
16 /**
17 Check for
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
22 namespace {
24 class CppunitAssertEquals:
25 public loplugin::FilteringPlugin<CppunitAssertEquals>
27 public:
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
38 if (preRun()) {
39 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
43 bool VisitCallExpr(const CallExpr*);
45 private:
46 void checkExpr(
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();
57 if (!decl)
58 return true;
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)
73 || ignoreLocation(
74 compiler.getSourceManager().getImmediateMacroCallerLoc(loc)))
76 return true;
78 auto name = Lexer::getImmediateMacroName(
79 loc, compiler.getSourceManager(), compiler.getLangOpts());
80 if (name != "CPPUNIT_ASSERT" && name != "CPPUNIT_ASSERT_MESSAGE") {
81 return true;
83 if (decl->getNumParams() != 3) {
84 report(
85 DiagnosticsEngine::Warning,
86 ("TODO: suspicious CppUnit::Asserter::failIf call with %0"
87 " parameters"),
88 callExpr->getExprLoc())
89 << decl->getNumParams() << callExpr->getSourceRange();
90 return true;
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) {
100 e2 = e4->getArg(0);
103 if (e2 == nullptr) {
104 report(
105 DiagnosticsEngine::Warning,
106 ("TODO: suspicious CppUnit::Asserter::failIf call not wrapping"
107 " !(...)"),
108 callExpr->getExprLoc())
109 << callExpr->getSourceRange();
110 return true;
112 auto range = compat::getImmediateExpansionRange(compiler.getSourceManager(), loc);
113 checkExpr(
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)))
126 return true;
127 if (isCompileTimeConstant(callExpr->getArg(1)))
128 report(
129 DiagnosticsEngine::Warning,
130 "CPPUNIT_ASSERT_EQUALS parameters look switched, expected value should be first param",
131 callExpr->getExprLoc())
132 << callExpr->getSourceRange();
134 return true;
137 // copied from stringconcat.cxx
138 Expr const * stripConstructor(Expr const * expr) {
139 auto e0 = expr;
140 auto const e1 = dyn_cast<CXXFunctionalCastExpr>(e0);
141 if (e1 != nullptr) {
142 e0 = e1->getSubExpr()->IgnoreParenImpCasts();
144 auto const e2 = dyn_cast<CXXBindTemporaryExpr>(e0);
145 if (e2 == nullptr) {
146 return expr;
148 auto const e3 = dyn_cast<CXXConstructExpr>(
149 e2->getSubExpr()->IgnoreParenImpCasts());
150 if (e3 == nullptr) {
151 return expr;
153 auto qt = loplugin::DeclCheck(e3->getConstructor());
154 if (!((qt.MemberFunction().Class("OString").Namespace("rtl")
155 .GlobalNamespace())
156 || (qt.MemberFunction().Class("OUString").Namespace("rtl")
157 .GlobalNamespace())))
159 return expr;
161 if (e3->getNumArgs() != 2) {
162 return expr;
164 return e3->getArg(0)->IgnoreParenImpCasts();
167 bool CppunitAssertEquals::isCompileTimeConstant(Expr const * expr)
169 if (expr->isIntegerConstantExpr(compiler.getASTContext()))
170 return true;
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) {
182 checkExpr(
183 range, name, e->getSubExpr()->IgnoreParenImpCasts(), !negated);
185 return;
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);
191 return;
193 #if 0 // TODO: enable later
194 if ((!negated && op == BO_LAnd) || (negated && op == BO_LOr)) {
195 report(
196 DiagnosticsEngine::Warning,
197 "rather split into two %0", e->getExprLoc())
198 << name << range;
199 return;
201 #endif
202 return;
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);
210 return;
212 return;
216 void CppunitAssertEquals::reportEquals(
217 SourceRange range, StringRef name, bool negative)
219 report(
220 DiagnosticsEngine::Warning,
221 ("rather call"
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)"),
225 range.getBegin())
226 << (name == "CPPUNIT_ASSERT_MESSAGE") << negative << range;
229 loplugin::Plugin::Registration< CppunitAssertEquals > cppunitassertequals("cppunitassertequals");
231 } // namespace
233 #endif // LO_CLANG_SHARED_PLUGINS
235 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */