diff options
Diffstat (limited to 'compilerplugins/clang/cppunitassertequals.cxx')
-rw-r--r-- | compilerplugins/clang/cppunitassertequals.cxx | 240 |
1 files changed, 240 insertions, 0 deletions
diff --git a/compilerplugins/clang/cppunitassertequals.cxx b/compilerplugins/clang/cppunitassertequals.cxx new file mode 100644 index 000000000..56f13b822 --- /dev/null +++ b/compilerplugins/clang/cppunitassertequals.cxx @@ -0,0 +1,240 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +#ifndef LO_CLANG_SHARED_PLUGINS + +#include "plugin.hxx" +#include "check.hxx" +#include "compat.hxx" +#include <iostream> + +/** + Check for + (*) calls to CPPUNIT_ASSERT when it should be using CPPUNIT_ASSERT_EQUALS + (*) calls to CPPUNIT_ASSERT_EQUALS where the constant is the second param +*/ + +namespace { + +class CppunitAssertEquals: + public loplugin::FilteringPlugin<CppunitAssertEquals> +{ +public: + explicit CppunitAssertEquals(loplugin::InstantiationData const & data): + FilteringPlugin(data) {} + + virtual bool preRun() override + { + return compiler.getLangOpts().CPlusPlus; + } + + virtual void run() override + { + if (preRun()) { + TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); + } + } + + bool VisitCallExpr(const CallExpr*); + +private: + void checkExpr( + SourceRange range, StringRef name, Expr const * expr, bool negated); + + void reportEquals( + SourceRange range, StringRef name, bool negative, Expr const * lhs, Expr const * rhs); + + bool isCompileTimeConstant(Expr const * expr); +}; + +bool CppunitAssertEquals::VisitCallExpr(const CallExpr* callExpr) +{ + auto const decl = callExpr->getDirectCallee(); + if (!decl) + return true; + /* + calls to CPPUNIT_ASSERT when it should be using CPPUNIT_ASSERT_EQUALS + */ + if (loplugin::DeclCheck(decl).Function("failIf").Struct("Asserter") + .Namespace("CppUnit").GlobalNamespace()) + { + // Don't use callExpr->getLocStart() or callExpr->getExprLoc(), as those + // fall into a nested use of the CPPUNIT_NS macro; CallExpr::getRParenLoc + // happens to be readily available and cause good results: + auto loc = callExpr->getRParenLoc(); + while (compiler.getSourceManager().isMacroArgExpansion(loc)) { + loc = compiler.getSourceManager().getImmediateMacroCallerLoc(loc); + } + if (!compiler.getSourceManager().isMacroBodyExpansion(loc) + || ignoreLocation( + compiler.getSourceManager().getImmediateMacroCallerLoc(loc))) + { + return true; + } + auto name = Lexer::getImmediateMacroName( + loc, compiler.getSourceManager(), compiler.getLangOpts()); + if (name != "CPPUNIT_ASSERT" && name != "CPPUNIT_ASSERT_MESSAGE") { + return true; + } + if (decl->getNumParams() != 3) { + report( + DiagnosticsEngine::Warning, + ("TODO: suspicious CppUnit::Asserter::failIf call with %0" + " parameters"), + callExpr->getExprLoc()) + << decl->getNumParams() << callExpr->getSourceRange(); + return true; + } + auto const e1 = callExpr->getArg(0)->IgnoreParenImpCasts(); + Expr const * e2 = nullptr; + if (auto const e3 = dyn_cast<UnaryOperator>(e1)) { + if (e3->getOpcode() == UO_LNot) { + e2 = e3->getSubExpr(); + } + } else if (auto const e4 = dyn_cast<CXXOperatorCallExpr>(e1)) { + if (e4->getOperator() == OO_Exclaim) { + e2 = e4->getArg(0); + } + } + if (e2 == nullptr) { + report( + DiagnosticsEngine::Warning, + ("TODO: suspicious CppUnit::Asserter::failIf call not wrapping" + " !(...)"), + callExpr->getExprLoc()) + << callExpr->getSourceRange(); + return true; + } + auto range = compat::getImmediateExpansionRange(compiler.getSourceManager(), loc); + checkExpr( + SourceRange(range.first, range.second), name, + e2->IgnoreParenImpCasts(), false); + } + + /** + Check for calls to CPPUNIT_ASSERT_EQUALS where the constant is the second param + */ + if (loplugin::DeclCheck(decl).Function("assertEquals"). + Namespace("CppUnit").GlobalNamespace()) + { + // can happen in template test code that both params are compile time constants + if (isCompileTimeConstant(callExpr->getArg(0))) + return true; + if (isCompileTimeConstant(callExpr->getArg(1))) + report( + DiagnosticsEngine::Warning, + "CPPUNIT_ASSERT_EQUALS parameters look switched, expected value should be first param", + callExpr->getExprLoc()) + << callExpr->getSourceRange(); + } + return true; +} + +// copied from stringconcat.cxx +Expr const * stripConstructor(Expr const * expr) { + auto e0 = expr; + auto const e1 = dyn_cast<CXXFunctionalCastExpr>(e0); + if (e1 != nullptr) { + e0 = e1->getSubExpr()->IgnoreParenImpCasts(); + } + auto const e2 = dyn_cast<CXXBindTemporaryExpr>(e0); + if (e2 == nullptr) { + return expr; + } + auto const e3 = dyn_cast<CXXConstructExpr>( + e2->getSubExpr()->IgnoreParenImpCasts()); + if (e3 == nullptr) { + return expr; + } + auto qt = loplugin::DeclCheck(e3->getConstructor()); + if (!((qt.MemberFunction().Class("OString").Namespace("rtl") + .GlobalNamespace()) + || (qt.MemberFunction().Class("OUString").Namespace("rtl") + .GlobalNamespace()))) + { + return expr; + } + if (e3->getNumArgs() != 2) { + return expr; + } + return e3->getArg(0)->IgnoreParenImpCasts(); +} + +bool CppunitAssertEquals::isCompileTimeConstant(Expr const * expr) +{ + if (expr->isIntegerConstantExpr(compiler.getASTContext())) + return true; + // is string literal ? + expr = expr->IgnoreParenImpCasts(); + expr = stripConstructor(expr); + return isa<clang::StringLiteral>(expr); +} + +void CppunitAssertEquals::checkExpr( + SourceRange range, StringRef name, Expr const * expr, bool negated) +{ + if (auto const e = dyn_cast<UnaryOperator>(expr)) { + if (e->getOpcode() == UO_LNot) { + checkExpr( + range, name, e->getSubExpr()->IgnoreParenImpCasts(), !negated); + } + return; + } + if (auto const e = dyn_cast<BinaryOperator>(expr)) { + auto const op = e->getOpcode(); + if ((!negated && op == BO_EQ) || (negated && op == BO_NE)) { + reportEquals(range, name, op == BO_NE, e->getLHS(), e->getRHS()); + return; + } + if ((!negated && op == BO_LAnd) || (negated && op == BO_LOr)) { + report( + DiagnosticsEngine::Warning, + "rather split into two %0", e->getExprLoc()) + << name << range; + return; + } + return; + } + if (auto const e = dyn_cast<CXXOperatorCallExpr>(expr)) { + auto const op = e->getOperator(); + if ((!negated && op == OO_EqualEqual) + || (negated && op == OO_ExclaimEqual)) + { + reportEquals(range, name, op == OO_ExclaimEqual, e->getArg(0), e->getArg(1)); + return; + } + return; + } +} + +void CppunitAssertEquals::reportEquals( + SourceRange range, StringRef name, bool negative, Expr const * lhs, Expr const * rhs) +{ + if (lhs->IgnoreImpCasts()->getType()->isNullPtrType() + != rhs->IgnoreImpCasts()->getType()->isNullPtrType()) + { + return; + } + report( + DiagnosticsEngine::Warning, + ("rather call" + " %select{CPPUNIT_ASSERT_EQUAL|CPPUNIT_ASSERT_EQUAL_MESSAGE}0 when comparing %1 and %2 (or" + " rewrite as an explicit operator %select{==|!=}3 call when the" + " operator itself is the topic)"), + range.getBegin()) + << (name == "CPPUNIT_ASSERT_MESSAGE") << lhs->IgnoreImpCasts()->getType() + << rhs->IgnoreImpCasts()->getType() << negative << range; +} + +loplugin::Plugin::Registration< CppunitAssertEquals > cppunitassertequals("cppunitassertequals"); + +} // namespace + +#endif // LO_CLANG_SHARED_PLUGINS + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |