/* -*- 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();
}
if (loplugin::DeclCheck(decl).Function(
"assertDoubleEquals").
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_DOUBLES_EQUALS parameters look switched, expected value should be first param",
callExpr->getExprLoc())
<< callExpr->getSourceRange();
}
if (loplugin::DeclCheck(decl).Function(
"assertLess").
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_LESS parameters look switched, expected value should be first param",
callExpr->getExprLoc())
<< callExpr->getSourceRange();
}
if (loplugin::DeclCheck(decl).Function(
"assertLessEqual").
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_LESSEQUAL parameters look switched, expected value should be first param",
callExpr->getExprLoc())
<< callExpr->getSourceRange();
}
if (loplugin::DeclCheck(decl).Function(
"assertGreater").
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_GREATER parameters look switched, expected value should be first param",
callExpr->getExprLoc())
<< callExpr->getSourceRange();
}
if (loplugin::DeclCheck(decl).Function(
"assertGreaterEqual").
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_GREATEREQUAL 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->isCXX11ConstantExpr(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: */