bump product version to 6.3.0.0.beta1
[LibreOffice.git] / compilerplugins / clang / cppunitassertequals.cxx
blob135a7e5c829af46458d6ecc8f9d23fe4e79ea827
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 */
10 #include "plugin.hxx"
11 #include "check.hxx"
12 #include "compat.hxx"
13 #include <iostream>
15 /**
16 Check for
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
21 namespace {
23 class CppunitAssertEquals:
24 public loplugin::FilteringPlugin<CppunitAssertEquals>
26 public:
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*);
39 private:
40 void checkExpr(
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();
51 if (!decl)
52 return true;
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)
67 || ignoreLocation(
68 compiler.getSourceManager().getImmediateMacroCallerLoc(loc)))
70 return true;
72 auto name = Lexer::getImmediateMacroName(
73 loc, compiler.getSourceManager(), compiler.getLangOpts());
74 if (name != "CPPUNIT_ASSERT" && name != "CPPUNIT_ASSERT_MESSAGE") {
75 return true;
77 if (decl->getNumParams() != 3) {
78 report(
79 DiagnosticsEngine::Warning,
80 ("TODO: suspicious CppUnit::Asserter::failIf call with %0"
81 " parameters"),
82 callExpr->getExprLoc())
83 << decl->getNumParams() << callExpr->getSourceRange();
84 return true;
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) {
94 e2 = e4->getArg(0);
97 if (e2 == nullptr) {
98 report(
99 DiagnosticsEngine::Warning,
100 ("TODO: suspicious CppUnit::Asserter::failIf call not wrapping"
101 " !(...)"),
102 callExpr->getExprLoc())
103 << callExpr->getSourceRange();
104 return true;
106 auto range = compat::getImmediateExpansionRange(compiler.getSourceManager(), loc);
107 checkExpr(
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)))
120 return true;
121 if (isCompileTimeConstant(callExpr->getArg(1)))
122 report(
123 DiagnosticsEngine::Warning,
124 "CPPUNIT_ASSERT_EQUALS parameters look switched, expected value should be first param",
125 callExpr->getExprLoc())
126 << callExpr->getSourceRange();
128 return true;
131 // copied from stringconcat.cxx
132 Expr const * stripCtor(Expr const * expr) {
133 auto e0 = expr;
134 auto const e1 = dyn_cast<CXXFunctionalCastExpr>(e0);
135 if (e1 != nullptr) {
136 e0 = e1->getSubExpr()->IgnoreParenImpCasts();
138 auto const e2 = dyn_cast<CXXBindTemporaryExpr>(e0);
139 if (e2 == nullptr) {
140 return expr;
142 auto const e3 = dyn_cast<CXXConstructExpr>(
143 e2->getSubExpr()->IgnoreParenImpCasts());
144 if (e3 == nullptr) {
145 return expr;
147 auto qt = loplugin::DeclCheck(e3->getConstructor());
148 if (!((qt.MemberFunction().Class("OString").Namespace("rtl")
149 .GlobalNamespace())
150 || (qt.MemberFunction().Class("OUString").Namespace("rtl")
151 .GlobalNamespace())))
153 return expr;
155 if (e3->getNumArgs() != 2) {
156 return expr;
158 return e3->getArg(0)->IgnoreParenImpCasts();
161 bool CppunitAssertEquals::isCompileTimeConstant(Expr const * expr)
163 if (expr->isIntegerConstantExpr(compiler.getASTContext()))
164 return true;
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) {
176 checkExpr(
177 range, name, e->getSubExpr()->IgnoreParenImpCasts(), !negated);
179 return;
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);
185 return;
187 #if 0 // TODO: enable later
188 if ((!negated && op == BO_LAnd) || (negated && op == BO_LOr)) {
189 report(
190 DiagnosticsEngine::Warning,
191 "rather split into two %0", e->getExprLoc())
192 << name << range;
193 return;
195 #endif
196 return;
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);
204 return;
206 return;
210 void CppunitAssertEquals::reportEquals(
211 SourceRange range, StringRef name, bool negative)
213 report(
214 DiagnosticsEngine::Warning,
215 ("rather call"
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)"),
219 range.getBegin())
220 << (name == "CPPUNIT_ASSERT_MESSAGE") << negative << range;
223 loplugin::Plugin::Registration< CppunitAssertEquals > X("cppunitassertequals");
227 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */