5d546de67b
<https://github.com/llvm/llvm-project/commit/ 5689b38c6a4220cc5f6ba68a56486229b10071bf> "Removed a RecursiveASTVisitor feature to visit operator kinds with different methods". That change is incompatible in that before the change individual TraverseUnary* and TraverseBin* functions were called, while now TraverseUnaryOperator and TraverseBinaryOperator/TraverseCompoundAssignOperator are called for all the different operators. Fixed that with a few #if for the non-shared plugins, but that doesn't work for the shared plugin. So made the two affected plugins non- shared for now and left a better fix as a TODO. Change-Id: I5b87d329ae2c4c93bf605bb1ecc9641039f014a3 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/99000 Tested-by: Jenkins Reviewed-by: Stephan Bergmann <sbergman@redhat.com>
907 lines
34 KiB
C++
907 lines
34 KiB
C++
/* -*- 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/.
|
|
*/
|
|
|
|
// Warn about certain redundant casts:
|
|
//
|
|
// * A reinterpret_cast<T*>(...) whose result is then implicitly cast to a void
|
|
// pointer
|
|
//
|
|
// * A static_cast<T*>(e) where e is of void pointer type and whose result is
|
|
// then implicitly cast to a void pointer
|
|
//
|
|
// * Various const_casts that are either not needed (like casting away constness
|
|
// in a delete expression) or are implicitly cast back afterwards
|
|
//
|
|
// C-style casts are ignored because it makes this plugin simpler, and they
|
|
// should eventually be eliminated via loplugin:cstylecast and/or
|
|
// -Wold-style-cast. That implies that this plugin is only relevant for C++
|
|
// code.
|
|
|
|
#include "clang/Sema/Sema.h"
|
|
|
|
#include "check.hxx"
|
|
#include "compat.hxx"
|
|
#include "plugin.hxx"
|
|
#include <iostream>
|
|
|
|
namespace {
|
|
|
|
bool isVoidPointer(QualType type) {
|
|
return type->isPointerType()
|
|
&& type->getAs<clang::PointerType>()->getPointeeType()->isVoidType();
|
|
}
|
|
|
|
bool isRedundantConstCast(CXXConstCastExpr const * expr) {
|
|
auto const sub = compat::getSubExprAsWritten(expr);
|
|
return
|
|
(expr->getType().getCanonicalType()
|
|
== sub->getType().getCanonicalType())
|
|
&& (expr->getValueKind() != VK_XValue
|
|
|| sub->getValueKind() == VK_XValue);
|
|
}
|
|
|
|
bool canConstCastFromTo(Expr const * from, Expr const * to) {
|
|
auto const k1 = from->getValueKind();
|
|
auto const k2 = to->getValueKind();
|
|
return (k2 == VK_LValue && k1 == VK_LValue)
|
|
|| (k2 == VK_XValue
|
|
&& (k1 != VK_RValue || from->getType()->isRecordType()));
|
|
}
|
|
|
|
char const * printExprValueKind(ExprValueKind k) {
|
|
switch (k) {
|
|
case VK_RValue:
|
|
return "prvalue";
|
|
case VK_LValue:
|
|
return "lvalue";
|
|
case VK_XValue:
|
|
return "xvalue";
|
|
};
|
|
llvm_unreachable("unknown ExprValueKind");
|
|
}
|
|
|
|
enum class AlgebraicType { None, Integer, FloatingPoint };
|
|
|
|
AlgebraicType algebraicType(clang::Type const & type) {
|
|
if (type.isIntegralOrEnumerationType()) {
|
|
return AlgebraicType::Integer;
|
|
} else if (type.isRealFloatingType()) {
|
|
return AlgebraicType::FloatingPoint;
|
|
} else {
|
|
return AlgebraicType::None;
|
|
}
|
|
}
|
|
|
|
// Do not look through FunctionToPointerDecay, but through e.g. NullToPointer:
|
|
Expr const * stopAtFunctionPointerDecay(ExplicitCastExpr const * expr) {
|
|
auto const e1 = expr->getSubExpr();
|
|
if (auto const e2 = dyn_cast<ImplicitCastExpr>(e1)) {
|
|
if (e2->getCastKind() != CK_FunctionToPointerDecay) {
|
|
return e2->getSubExpr();
|
|
}
|
|
}
|
|
return e1;
|
|
}
|
|
|
|
class RedundantCast:
|
|
public loplugin::FilteringRewritePlugin<RedundantCast>
|
|
{
|
|
public:
|
|
explicit RedundantCast(loplugin::InstantiationData const & data):
|
|
FilteringRewritePlugin(data)
|
|
{}
|
|
|
|
virtual void run() override {
|
|
if (compiler.getLangOpts().CPlusPlus) {
|
|
TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
|
|
}
|
|
}
|
|
|
|
bool TraverseInitListExpr(InitListExpr * expr, DataRecursionQueue * queue = nullptr) {
|
|
return WalkUpFromInitListExpr(expr)
|
|
&& TraverseSynOrSemInitListExpr(
|
|
expr->isSemanticForm() ? expr : expr->getSemanticForm(), queue);
|
|
}
|
|
|
|
bool VisitImplicitCastExpr(ImplicitCastExpr const * expr);
|
|
|
|
bool VisitCXXStaticCastExpr(CXXStaticCastExpr const * expr);
|
|
|
|
bool VisitCXXReinterpretCastExpr(CXXReinterpretCastExpr const * expr);
|
|
|
|
bool VisitCXXConstCastExpr(CXXConstCastExpr const * expr);
|
|
|
|
bool VisitCXXFunctionalCastExpr(CXXFunctionalCastExpr const * expr);
|
|
|
|
bool VisitCXXDynamicCastExpr(CXXDynamicCastExpr const * expr);
|
|
|
|
bool VisitCallExpr(CallExpr const * expr);
|
|
|
|
bool VisitCXXDeleteExpr(CXXDeleteExpr const * expr);
|
|
|
|
bool VisitCStyleCastExpr(CStyleCastExpr const * expr);
|
|
|
|
bool VisitBinaryOperator(BinaryOperator const * expr) {
|
|
auto const op = expr->getOpcode();
|
|
if (op == BO_Sub || expr->isRelationalOp() || expr->isEqualityOp()) {
|
|
return visitBinOp(expr);
|
|
}
|
|
if (op == BO_Assign) {
|
|
if (ignoreLocation(expr)) {
|
|
return true;
|
|
}
|
|
visitAssign(expr->getLHS()->getType(), expr->getRHS());
|
|
return true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool VisitVarDecl(VarDecl const * varDecl);
|
|
|
|
private:
|
|
bool visitBinOp(BinaryOperator const * expr);
|
|
void visitAssign(QualType lhs, Expr const * rhs);
|
|
bool isOverloadedFunction(FunctionDecl const * decl);
|
|
|
|
bool isInIgnoredMacroBody(Expr const * expr) {
|
|
auto const loc = compat::getBeginLoc(expr);
|
|
return compiler.getSourceManager().isMacroBodyExpansion(loc)
|
|
&& ignoreLocation(compiler.getSourceManager().getSpellingLoc(loc));
|
|
}
|
|
};
|
|
|
|
bool RedundantCast::VisitImplicitCastExpr(const ImplicitCastExpr * expr) {
|
|
if (ignoreLocation(expr)) {
|
|
return true;
|
|
}
|
|
switch (expr->getCastKind()) {
|
|
case CK_NoOp:
|
|
if (expr->getType()->isPointerType()
|
|
|| expr->getType()->isObjectType())
|
|
{
|
|
auto e = dyn_cast<CXXConstCastExpr>(
|
|
expr->getSubExpr()->IgnoreParenImpCasts());
|
|
if (e != nullptr && !isRedundantConstCast(e)) {
|
|
auto t1 = e->getSubExpr()->getType().getCanonicalType();
|
|
auto t3 = expr->getType().getCanonicalType();
|
|
bool ObjCLifetimeConversion;
|
|
if (t1.getTypePtr() == t3.getTypePtr()
|
|
|| (compiler.getSema().IsQualificationConversion(
|
|
t1, t3, false, ObjCLifetimeConversion)
|
|
&& (e->getType().getCanonicalType().getTypePtr()
|
|
!= t3.getTypePtr())))
|
|
{
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("redundant const_cast from %0 to %1, result is"
|
|
" implicitly cast to %2"),
|
|
e->getExprLoc())
|
|
<< e->getSubExprAsWritten()->getType() << e->getType()
|
|
<< expr->getType() << expr->getSourceRange();
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case CK_BitCast:
|
|
if (isVoidPointer(expr->getType())
|
|
&& expr->getSubExpr()->getType()->isPointerType())
|
|
{
|
|
Expr const * e = expr->getSubExpr()->IgnoreParenImpCasts();
|
|
while (isa<CXXConstCastExpr>(e)) {
|
|
auto cc = dyn_cast<CXXConstCastExpr>(e);
|
|
if (expr->getType()->getAs<clang::PointerType>()
|
|
->getPointeeType().isAtLeastAsQualifiedAs(
|
|
cc->getSubExpr()->getType()
|
|
->getAs<clang::PointerType>()->getPointeeType()))
|
|
{
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("redundant const_cast from %0 to %1, result is"
|
|
" ultimately implicitly cast to %2"),
|
|
cc->getExprLoc())
|
|
<< cc->getSubExprAsWritten()->getType() << cc->getType()
|
|
<< expr->getType() << expr->getSourceRange();
|
|
}
|
|
e = cc->getSubExpr()->IgnoreParenImpCasts();
|
|
}
|
|
if (isa<CXXReinterpretCastExpr>(e)) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("redundant reinterpret_cast, result is implicitly cast to"
|
|
" void pointer"),
|
|
e->getExprLoc())
|
|
<< e->getSourceRange();
|
|
} else if (isa<CXXStaticCastExpr>(e)
|
|
&& isVoidPointer(
|
|
dyn_cast<CXXStaticCastExpr>(e)->getSubExpr()
|
|
->IgnoreParenImpCasts()->getType())
|
|
&& !compiler.getSourceManager().isMacroBodyExpansion(
|
|
compat::getBeginLoc(e)))
|
|
{
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("redundant static_cast from void pointer, result is"
|
|
" implicitly cast to void pointer"),
|
|
e->getExprLoc())
|
|
<< e->getSourceRange();
|
|
}
|
|
}
|
|
break;
|
|
case CK_DerivedToBase:
|
|
case CK_UncheckedDerivedToBase:
|
|
if (expr->getType()->isPointerType()) {
|
|
Expr const * e = expr->getSubExpr()->IgnoreParenImpCasts();
|
|
while (isa<CXXConstCastExpr>(e)) {
|
|
auto cc = dyn_cast<CXXConstCastExpr>(e);
|
|
if (expr->getType()->getAs<clang::PointerType>()
|
|
->getPointeeType().isAtLeastAsQualifiedAs(
|
|
cc->getSubExpr()->getType()
|
|
->getAs<clang::PointerType>()->getPointeeType()))
|
|
{
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("redundant const_cast from %0 to %1, result is"
|
|
" ultimately implicitly cast to %2"),
|
|
cc->getExprLoc())
|
|
<< cc->getSubExprAsWritten()->getType() << cc->getType()
|
|
<< expr->getType() << expr->getSourceRange();
|
|
}
|
|
e = cc->getSubExpr()->IgnoreParenImpCasts();
|
|
}
|
|
} else if (expr->getType()->isReferenceType()) {
|
|
Expr const * e = expr->getSubExpr()->IgnoreParenImpCasts();
|
|
while (isa<CXXConstCastExpr>(e)) {
|
|
auto cc = dyn_cast<CXXConstCastExpr>(e);
|
|
if (expr->getType()->getAs<ReferenceType>()->getPointeeType()
|
|
.isAtLeastAsQualifiedAs(
|
|
cc->getSubExpr()->getType()
|
|
->getAs<ReferenceType>()->getPointeeType()))
|
|
{
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("redundant const_cast from %0 to %1, result is"
|
|
" ultimately implicitly cast to %2"),
|
|
cc->getExprLoc())
|
|
<< cc->getSubExprAsWritten()->getType() << cc->getType()
|
|
<< expr->getType() << expr->getSourceRange();
|
|
}
|
|
e = cc->getSubExpr()->IgnoreParenImpCasts();
|
|
}
|
|
}
|
|
break;
|
|
case CK_FloatingToIntegral:
|
|
case CK_IntegralToFloating:
|
|
if (auto e = dyn_cast<ExplicitCastExpr>(expr->getSubExpr()->IgnoreParenImpCasts())) {
|
|
if ((isa<CXXStaticCastExpr>(e) || isa<CXXFunctionalCastExpr>(e))
|
|
&& (algebraicType(*e->getSubExprAsWritten()->getType())
|
|
== algebraicType(*expr->getType())))
|
|
{
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("suspicious %select{static_cast|functional cast}0 from %1 to %2, result is"
|
|
" implicitly cast to %3"),
|
|
e->getExprLoc())
|
|
<< isa<CXXFunctionalCastExpr>(e) << e->getSubExprAsWritten()->getType()
|
|
<< e->getTypeAsWritten() << expr->getType() << expr->getSourceRange();
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool RedundantCast::VisitCStyleCastExpr(CStyleCastExpr const * expr) {
|
|
if (ignoreLocation(expr)) {
|
|
return true;
|
|
}
|
|
if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(compat::getBeginLoc(expr)))) {
|
|
return true;
|
|
}
|
|
auto t1 = compat::getSubExprAsWritten(expr)->getType();
|
|
auto t2 = expr->getTypeAsWritten();
|
|
if (auto templateType = dyn_cast<SubstTemplateTypeParmType>(t1)) {
|
|
t1 = templateType->desugar();
|
|
}
|
|
if (t1 != t2) {
|
|
return true;
|
|
}
|
|
if (!t1->isBuiltinType() && !loplugin::TypeCheck(t1).Enum() && !loplugin::TypeCheck(t1).Typedef()) {
|
|
return true;
|
|
}
|
|
if (!loplugin::isOkToRemoveArithmeticCast(compiler.getASTContext(), t1, t2, expr->getSubExpr()))
|
|
{
|
|
return true;
|
|
}
|
|
// Ignore FD_ISSET expanding to "...(SOCKET)(fd)..." in some Microsoft
|
|
// winsock2.h (TODO: improve heuristic of determining that the whole
|
|
// expr is part of a single macro body expansion):
|
|
auto l1 = compat::getBeginLoc(expr);
|
|
while (compiler.getSourceManager().isMacroArgExpansion(l1)) {
|
|
l1 = compiler.getSourceManager().getImmediateMacroCallerLoc(l1);
|
|
}
|
|
auto l2 = expr->getExprLoc();
|
|
while (compiler.getSourceManager().isMacroArgExpansion(l2)) {
|
|
l2 = compiler.getSourceManager().getImmediateMacroCallerLoc(l2);
|
|
}
|
|
auto l3 = compat::getEndLoc(expr);
|
|
while (compiler.getSourceManager().isMacroArgExpansion(l3)) {
|
|
l3 = compiler.getSourceManager().getImmediateMacroCallerLoc(l3);
|
|
}
|
|
if (compiler.getSourceManager().isMacroBodyExpansion(l1)
|
|
&& compiler.getSourceManager().isMacroBodyExpansion(l2)
|
|
&& compiler.getSourceManager().isMacroBodyExpansion(l3)
|
|
&& ignoreLocation(compiler.getSourceManager().getSpellingLoc(l2)))
|
|
{
|
|
return true;
|
|
}
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
"redundant cstyle cast from %0 to %1", expr->getExprLoc())
|
|
<< t1 << t2 << expr->getSourceRange();
|
|
return true;
|
|
}
|
|
|
|
bool RedundantCast::VisitVarDecl(VarDecl const * varDecl) {
|
|
if (ignoreLocation(varDecl)) {
|
|
return true;
|
|
}
|
|
if (!varDecl->getInit())
|
|
return true;
|
|
visitAssign(varDecl->getType(), varDecl->getInit());
|
|
return true;
|
|
}
|
|
|
|
void RedundantCast::visitAssign(QualType t1, Expr const * rhs)
|
|
{
|
|
auto staticCastExpr = dyn_cast<CXXStaticCastExpr>(rhs->IgnoreImplicit());
|
|
if (!staticCastExpr)
|
|
return;
|
|
|
|
auto const t2 = staticCastExpr->getSubExpr()->IgnoreImplicit()->getType();
|
|
|
|
// if there is more than one copy of the LHS, this cast is resolving ambiguity
|
|
bool foundOne = false;
|
|
if (t1->isRecordType())
|
|
{
|
|
foundOne = loplugin::derivedFromCount(t2, t1) == 1;
|
|
}
|
|
else
|
|
{
|
|
auto pointee1 = t1->getPointeeCXXRecordDecl();
|
|
auto pointee2 = t2->getPointeeCXXRecordDecl();
|
|
if (pointee1 && pointee2)
|
|
foundOne = loplugin::derivedFromCount(pointee2, pointee1) == 1;
|
|
}
|
|
|
|
if (foundOne)
|
|
{
|
|
report(
|
|
DiagnosticsEngine::Warning, "redundant static_cast from %0 to %1",
|
|
staticCastExpr->getExprLoc())
|
|
<< t2 << t1 << staticCastExpr->getSourceRange();
|
|
}
|
|
}
|
|
|
|
bool RedundantCast::VisitCXXStaticCastExpr(CXXStaticCastExpr const * expr) {
|
|
if (ignoreLocation(expr)) {
|
|
return true;
|
|
}
|
|
auto const t2 = expr->getTypeAsWritten();
|
|
bool const fnptr = t2->isFunctionPointerType() || t2->isMemberFunctionPointerType();
|
|
auto const sub = fnptr ? stopAtFunctionPointerDecay(expr) : compat::getSubExprAsWritten(expr);
|
|
auto const t1 = sub->getType();
|
|
auto const nonClassObjectType = t2->isObjectType()
|
|
&& !(t2->isRecordType() || t2->isArrayType());
|
|
if (nonClassObjectType && t2.hasLocalQualifiers()) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("in static_cast from %0 %1 to %2 %3, remove redundant top-level"
|
|
" %select{const qualifier|volatile qualifier|const volatile"
|
|
" qualifiers}4"),
|
|
expr->getExprLoc())
|
|
<< t1 << printExprValueKind(sub->getValueKind())
|
|
<< t2 << printExprValueKind(expr->getValueKind())
|
|
<< ((t2.isLocalConstQualified() ? 1 : 0)
|
|
+ (t2.isLocalVolatileQualified() ? 2 : 0) - 1)
|
|
<< expr->getSourceRange();
|
|
return true;
|
|
}
|
|
if (auto const impl = dyn_cast<ImplicitCastExpr>(expr->getSubExpr())) {
|
|
if (impl->getCastKind() == CK_ArrayToPointerDecay && impl->getType() == t2)
|
|
//TODO: instead of exact QualType match, allow some variation?
|
|
{
|
|
auto const fn = handler.getMainFileName();
|
|
if (!(loplugin::isSamePathname(
|
|
fn, SRCDIR "/sal/qa/rtl/strings/test_ostring_concat.cxx")
|
|
|| loplugin::isSamePathname(
|
|
fn, SRCDIR "/sal/qa/rtl/strings/test_ostring_stringliterals.cxx")
|
|
|| loplugin::isSamePathname(
|
|
fn, SRCDIR "/sal/qa/rtl/strings/test_oustring_concat.cxx")
|
|
|| loplugin::isSamePathname(
|
|
fn, SRCDIR "/sal/qa/rtl/strings/test_oustring_stringliterals.cxx")
|
|
|| isInIgnoredMacroBody(expr)))
|
|
{
|
|
report(
|
|
DiagnosticsEngine::Warning, "redundant static_cast from %0 to %1",
|
|
expr->getExprLoc())
|
|
<< expr->getSubExprAsWritten()->getType() << t2 << expr->getSourceRange();
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
auto const t3 = expr->getType();
|
|
auto const c1 = t1.getCanonicalType();
|
|
auto const c3 = t3.getCanonicalType();
|
|
if (nonClassObjectType || !canConstCastFromTo(sub, expr)
|
|
? c1.getTypePtr() != c3.getTypePtr() : c1 != c3)
|
|
{
|
|
bool ObjCLifetimeConversion;
|
|
if (nonClassObjectType
|
|
|| (c1.getTypePtr() != c3.getTypePtr()
|
|
&& !compiler.getSema().IsQualificationConversion(
|
|
c1, c3, false, ObjCLifetimeConversion)))
|
|
{
|
|
return true;
|
|
}
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
"static_cast from %0 %1 to %2 %3 should be written as const_cast",
|
|
expr->getExprLoc())
|
|
<< t1 << printExprValueKind(sub->getValueKind())
|
|
<< t2 << printExprValueKind(expr->getValueKind())
|
|
<< expr->getSourceRange();
|
|
return true;
|
|
}
|
|
if (!loplugin::isOkToRemoveArithmeticCast(compiler.getASTContext(), t1, t2, expr->getSubExpr()))
|
|
{
|
|
return true;
|
|
}
|
|
// Don't warn if the types are 'void *' and at least one involves a typedef
|
|
// (and if both involve typedefs, they're different) (this covers cases like
|
|
// 'oslModule', or 'CURL *', or casts between 'LPVOID' and 'HANDLE' in
|
|
// Windows-only code):
|
|
if (loplugin::TypeCheck(t1).Pointer().NonConstVolatile().Void()) {
|
|
if (auto const td1 = t1->getAs<TypedefType>()) {
|
|
auto const td2 = t2->getAs<TypedefType>();
|
|
if (td2 == nullptr || td2 != td1) {
|
|
return true;
|
|
}
|
|
} else if (auto const td2 = t2->getAs<TypedefType>()) {
|
|
auto const td1 = t1->getAs<TypedefType>();
|
|
if (td1 == nullptr || td1 != td2) {
|
|
return true;
|
|
}
|
|
} else {
|
|
auto const pt1 = t1->getAs<clang::PointerType>()->getPointeeType();
|
|
auto const pt2 = t2->getAs<clang::PointerType>()->getPointeeType();
|
|
if (auto const ptd1 = pt1->getAs<TypedefType>()) {
|
|
auto const ptd2 = pt2->getAs<TypedefType>();
|
|
if (ptd2 == nullptr || ptd2 != ptd1) {
|
|
return true;
|
|
}
|
|
} else if (auto const ptd2 = pt2->getAs<TypedefType>()) {
|
|
auto const ptd1 = pt1->getAs<TypedefType>();
|
|
if (ptd1 == nullptr || ptd1 != ptd2) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
auto const k1 = sub->getValueKind();
|
|
auto const k3 = expr->getValueKind();
|
|
if ((k3 == VK_XValue && k1 != VK_XValue)
|
|
|| (k3 == VK_LValue && k1 == VK_XValue))
|
|
{
|
|
return true;
|
|
}
|
|
// Don't warn if a static_cast on a pointer to function or member function is used to
|
|
// disambiguate an overloaded function:
|
|
if (fnptr) {
|
|
auto e = sub->IgnoreParenImpCasts();
|
|
if (auto const e1 = dyn_cast<UnaryOperator>(e)) {
|
|
if (e1->getOpcode() == UO_AddrOf) {
|
|
e = e1->getSubExpr()->IgnoreParenImpCasts();
|
|
}
|
|
}
|
|
if (auto const e1 = dyn_cast<DeclRefExpr>(e)) {
|
|
if (auto const fdecl = dyn_cast<FunctionDecl>(e1->getDecl())) {
|
|
if (isOverloadedFunction(fdecl)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Suppress warnings from static_cast<bool> in C++ definition of assert in
|
|
// <https://sourceware.org/git/?p=glibc.git;a=commit;
|
|
// h=b5889d25e9bf944a89fdd7bcabf3b6c6f6bb6f7c> "assert: Support types
|
|
// without operator== (int) [BZ #21972]":
|
|
if (t1->isBooleanType() && t2->isBooleanType()) {
|
|
auto loc = compat::getBeginLoc(expr);
|
|
if (compiler.getSourceManager().isMacroBodyExpansion(loc)
|
|
&& (Lexer::getImmediateMacroName(
|
|
loc, compiler.getSourceManager(), compiler.getLangOpts())
|
|
== "assert"))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("static_cast from %0 %1 to %2 %3 is redundant%select{| or should be"
|
|
" written as an explicit construction of a temporary}4"),
|
|
expr->getExprLoc())
|
|
<< t1 << printExprValueKind(k1) << t2 << printExprValueKind(k3)
|
|
<< (k3 == VK_RValue && (k1 != VK_RValue || t1->isRecordType()))
|
|
<< expr->getSourceRange();
|
|
return true;
|
|
}
|
|
|
|
bool RedundantCast::VisitCXXReinterpretCastExpr(
|
|
CXXReinterpretCastExpr const * expr)
|
|
{
|
|
if (ignoreLocation(expr)) {
|
|
return true;
|
|
}
|
|
if (auto const sub = dyn_cast<ImplicitCastExpr>(expr->getSubExpr())) {
|
|
if (sub->getCastKind() == CK_ArrayToPointerDecay && sub->getType() == expr->getType())
|
|
//TODO: instead of exact QualType match, allow some variation?
|
|
{
|
|
if (loplugin::TypeCheck(sub->getType()).Pointer().Const().Char()) {
|
|
if (auto const lit = dyn_cast<clang::StringLiteral>(expr->getSubExprAsWritten())) {
|
|
if (lit->getKind() == clang::StringLiteral::UTF8) {
|
|
// Don't warn about
|
|
//
|
|
// redundant_cast<char const *>(u8"...")
|
|
//
|
|
// in pre-C++2a code:
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
report(
|
|
DiagnosticsEngine::Warning, "redundant reinterpret_cast from %0 to %1",
|
|
expr->getExprLoc())
|
|
<< expr->getSubExprAsWritten()->getType() << expr->getTypeAsWritten()
|
|
<< expr->getSourceRange();
|
|
return true;
|
|
}
|
|
}
|
|
if (expr->getSubExpr()->getType()->isVoidPointerType()) {
|
|
auto t = expr->getType()->getAs<clang::PointerType>();
|
|
if (t == nullptr || !t->getPointeeType()->isObjectType()) {
|
|
return true;
|
|
}
|
|
if (rewriter != nullptr) {
|
|
auto loc = compat::getBeginLoc(expr);
|
|
while (compiler.getSourceManager().isMacroArgExpansion(loc)) {
|
|
loc = compiler.getSourceManager().getImmediateMacroCallerLoc(
|
|
loc);
|
|
}
|
|
if (compiler.getSourceManager().isMacroBodyExpansion(loc)) {
|
|
auto loc2 = compat::getEndLoc(expr);
|
|
while (compiler.getSourceManager().isMacroArgExpansion(loc2)) {
|
|
loc2 = compiler.getSourceManager()
|
|
.getImmediateMacroCallerLoc(loc2);
|
|
}
|
|
if (compiler.getSourceManager().isMacroBodyExpansion(loc2)) {
|
|
//TODO: check loc, loc2 are in same macro body expansion
|
|
loc = compiler.getSourceManager().getSpellingLoc(loc);
|
|
}
|
|
}
|
|
auto s = compiler.getSourceManager().getCharacterData(loc);
|
|
auto n = Lexer::MeasureTokenLength(
|
|
loc, compiler.getSourceManager(), compiler.getLangOpts());
|
|
std::string tok(s, n);
|
|
if (tok == "reinterpret_cast" && replaceText(loc, n, "static_cast"))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
"reinterpret_cast from %0 to %1 can be simplified to static_cast",
|
|
expr->getExprLoc())
|
|
<< expr->getSubExprAsWritten()->getType() << expr->getType()
|
|
<< expr->getSourceRange();
|
|
} else if (expr->getType()->isVoidPointerType()) {
|
|
auto t = expr->getSubExpr()->getType()->getAs<clang::PointerType>();
|
|
if (t == nullptr || !t->getPointeeType()->isObjectType()) {
|
|
return true;
|
|
}
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("reinterpret_cast from %0 to %1 can be simplified to static_cast"
|
|
" or an implicit conversion"),
|
|
expr->getExprLoc())
|
|
<< expr->getSubExprAsWritten()->getType() << expr->getType()
|
|
<< expr->getSourceRange();
|
|
} else if (expr->getType()->isFundamentalType()) {
|
|
if (auto const sub = dyn_cast<CXXConstCastExpr>(
|
|
expr->getSubExpr()->IgnoreParens()))
|
|
{
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("redundant const_cast from %0 to %1 within reinterpret_cast to"
|
|
" fundamental type %2"),
|
|
expr->getExprLoc())
|
|
<< sub->getSubExprAsWritten()->getType()
|
|
<< sub->getTypeAsWritten() << expr->getTypeAsWritten()
|
|
<< expr->getSourceRange();
|
|
return true;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool RedundantCast::VisitCXXConstCastExpr(CXXConstCastExpr const * expr) {
|
|
if (ignoreLocation(expr)) {
|
|
return true;
|
|
}
|
|
auto const sub = compat::getSubExprAsWritten(expr);
|
|
if (isRedundantConstCast(expr)) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
"redundant const_cast from %0 %1 to %2 %3", expr->getExprLoc())
|
|
<< sub->getType() << printExprValueKind(sub->getValueKind())
|
|
<< expr->getTypeAsWritten()
|
|
<< printExprValueKind(expr->getValueKind())
|
|
<< expr->getSourceRange();
|
|
return true;
|
|
}
|
|
if (auto const dce = dyn_cast<CXXStaticCastExpr>(
|
|
sub->IgnoreParenImpCasts()))
|
|
{
|
|
auto const sub2 = compat::getSubExprAsWritten(dce);
|
|
auto t1 = sub2->getType().getCanonicalType();
|
|
auto isNullptr = t1->isNullPtrType();
|
|
auto t2 = dce->getType().getCanonicalType();
|
|
auto t3 = expr->getType().getCanonicalType();
|
|
auto redundant = false;
|
|
for (;;) {
|
|
if ((t2.isConstQualified()
|
|
&& (isNullptr || !t1.isConstQualified())
|
|
&& !t3.isConstQualified())
|
|
|| (t2.isVolatileQualified()
|
|
&& (isNullptr || !t1.isVolatileQualified())
|
|
&& !t3.isVolatileQualified()))
|
|
{
|
|
redundant = true;
|
|
break;
|
|
}
|
|
if (!isNullptr) {
|
|
auto const p1 = t1->getAs<clang::PointerType>();
|
|
if (p1 == nullptr) {
|
|
break;
|
|
}
|
|
t1 = p1->getPointeeType();
|
|
isNullptr = t1->isNullPtrType();
|
|
}
|
|
auto const p2 = t2->getAs<clang::PointerType>();
|
|
if (p2 == nullptr) {
|
|
break;
|
|
}
|
|
t2 = p2->getPointeeType();
|
|
auto const p3 = t3->getAs<clang::PointerType>();
|
|
if (p3 == nullptr) {
|
|
break;
|
|
}
|
|
t3 = p3->getPointeeType();
|
|
}
|
|
if (redundant) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
("redundant static_cast/const_cast combination from %0 via %1"
|
|
" to %2"),
|
|
expr->getExprLoc())
|
|
<< sub2->getType() << dce->getTypeAsWritten()
|
|
<< expr->getTypeAsWritten() << expr->getSourceRange();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool RedundantCast::VisitCXXFunctionalCastExpr(CXXFunctionalCastExpr const * expr) {
|
|
if (ignoreLocation(expr)) {
|
|
return true;
|
|
}
|
|
// Restrict this to "real" casts (compared to uses of braced-init-list, like
|
|
//
|
|
// Foo{bar, baz}
|
|
//
|
|
// or
|
|
//
|
|
// std::initializer_list<Foo>{bar, baz}
|
|
//
|
|
// ), and only to cases where the sub-expression already is a prvalue of
|
|
// non-class type (and thus the cast is unlikely to be meant to create a
|
|
// temporary):
|
|
auto const t1 = expr->getTypeAsWritten();
|
|
bool const fnptr = t1->isFunctionPointerType() || t1->isMemberFunctionPointerType();
|
|
auto const sub = fnptr ? stopAtFunctionPointerDecay(expr) : compat::getSubExprAsWritten(expr);
|
|
if ((sub->getValueKind() != VK_RValue && !fnptr) || expr->getType()->isRecordType()
|
|
|| isa<InitListExpr>(sub) || isa<CXXStdInitializerListExpr>(sub))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// See "There might even be good reasons(?) not to warn inside explicit
|
|
// casts" block in compilerplugins/clang/test/cppunitassertequals.cxx:
|
|
auto const eloc = expr->getExprLoc();
|
|
if (compiler.getSourceManager().isMacroArgExpansion(eloc)) {
|
|
auto const name = Lexer::getImmediateMacroName(
|
|
eloc, compiler.getSourceManager(), compiler.getLangOpts());
|
|
if (name == "CPPUNIT_ASSERT" || name == "CPPUNIT_ASSERT_MESSAGE") {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Don't warn if a functional cast on a pointer to function or member function is used to
|
|
// disambiguate an overloaded function:
|
|
if (fnptr) {
|
|
auto e = sub->IgnoreParenImpCasts();
|
|
if (auto const e1 = dyn_cast<UnaryOperator>(e)) {
|
|
if (e1->getOpcode() == UO_AddrOf) {
|
|
e = e1->getSubExpr()->IgnoreParenImpCasts();
|
|
}
|
|
}
|
|
if (auto const e1 = dyn_cast<DeclRefExpr>(e)) {
|
|
if (auto const fdecl = dyn_cast<FunctionDecl>(e1->getDecl())) {
|
|
if (isOverloadedFunction(fdecl)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// See the commit message of d0e7d020fa405ab94f19916ec96fbd4611da0031
|
|
// "socket.c -> socket.cxx" for the reason to have
|
|
//
|
|
// bool(FD_ISSET(...))
|
|
//
|
|
// in sal/osl/unx/socket.cxx:
|
|
//TODO: Better check that sub is exactly an expansion of FD_ISSET:
|
|
if (compat::getEndLoc(sub).isMacroID()) {
|
|
for (auto loc = compat::getBeginLoc(sub);
|
|
loc.isMacroID()
|
|
&& (compiler.getSourceManager()
|
|
.isAtStartOfImmediateMacroExpansion(loc));
|
|
loc = compiler.getSourceManager().getImmediateMacroCallerLoc(loc))
|
|
{
|
|
if (Lexer::getImmediateMacroName(
|
|
loc, compiler.getSourceManager(), compiler.getLangOpts())
|
|
== "FD_ISSET")
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
auto const t2 = sub->getType();
|
|
if (t1.getCanonicalType() != t2.getCanonicalType())
|
|
return true;
|
|
if (!loplugin::isOkToRemoveArithmeticCast(compiler.getASTContext(), t1, t2, expr->getSubExpr()))
|
|
return true;
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
"redundant functional cast from %0 to %1", expr->getExprLoc())
|
|
<< t2 << t1 << expr->getSourceRange();
|
|
return true;
|
|
}
|
|
|
|
bool RedundantCast::VisitCXXDynamicCastExpr(CXXDynamicCastExpr const * expr) {
|
|
if (ignoreLocation(expr)) {
|
|
return true;
|
|
}
|
|
// so far this only deals with dynamic casting from T to T
|
|
auto const sub = compat::getSubExprAsWritten(expr);
|
|
auto const t1 = expr->getTypeAsWritten();
|
|
auto const t2 = sub->getType();
|
|
if (t1.getCanonicalType() != t2.getCanonicalType())
|
|
return true;
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
"redundant dynamic cast from %0 to %1", expr->getExprLoc())
|
|
<< t2 << t1 << expr->getSourceRange();
|
|
return true;
|
|
}
|
|
|
|
bool RedundantCast::VisitCallExpr(CallExpr const * expr) {
|
|
if (ignoreLocation(expr)) {
|
|
return true;
|
|
}
|
|
auto f = expr->getDirectCallee();
|
|
if (f == nullptr || !f->isVariadic()
|
|
|| expr->getNumArgs() <= f->getNumParams())
|
|
{
|
|
return true;
|
|
}
|
|
for (auto i = f->getNumParams(); i != expr->getNumArgs(); ++i) {
|
|
auto a = expr->getArg(i);
|
|
if (a->getType()->isPointerType()) {
|
|
auto e = dyn_cast<CXXConstCastExpr>(a->IgnoreParenImpCasts());
|
|
if (e != nullptr) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
"redundant const_cast of variadic function argument",
|
|
e->getExprLoc())
|
|
<< expr->getSourceRange();
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool RedundantCast::VisitCXXDeleteExpr(CXXDeleteExpr const * expr) {
|
|
if (ignoreLocation(expr)) {
|
|
return true;
|
|
}
|
|
auto e = dyn_cast<CXXConstCastExpr>(
|
|
expr->getArgument()->IgnoreParenImpCasts());
|
|
if (e != nullptr) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
"redundant const_cast in delete expression", e->getExprLoc())
|
|
<< expr->getSourceRange();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool RedundantCast::visitBinOp(BinaryOperator const * expr) {
|
|
if (ignoreLocation(expr)) {
|
|
return true;
|
|
}
|
|
if (expr->getLHS()->getType()->isPointerType()
|
|
&& expr->getRHS()->getType()->isPointerType())
|
|
{
|
|
auto e = dyn_cast<CXXConstCastExpr>(
|
|
expr->getLHS()->IgnoreParenImpCasts());
|
|
if (e != nullptr) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
"redundant const_cast on lhs of pointer %select{comparison|subtraction}0 expression",
|
|
e->getExprLoc())
|
|
<< (expr->getOpcode() == BO_Sub) << expr->getSourceRange();
|
|
}
|
|
e = dyn_cast<CXXConstCastExpr>(
|
|
expr->getRHS()->IgnoreParenImpCasts());
|
|
if (e != nullptr) {
|
|
report(
|
|
DiagnosticsEngine::Warning,
|
|
"redundant const_cast on rhs of pointer %select{comparison|subtraction}0 expression",
|
|
e->getExprLoc())
|
|
<< (expr->getOpcode() == BO_Sub) << expr->getSourceRange();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool RedundantCast::isOverloadedFunction(FunctionDecl const * decl) {
|
|
auto const ctx = decl->getDeclContext();
|
|
if (!ctx->isLookupContext()) {
|
|
return false;
|
|
}
|
|
auto const canon = decl->getCanonicalDecl();
|
|
auto const res = ctx->lookup(decl->getDeclName());
|
|
for (auto d = res.begin(); d != res.end(); ++d) {
|
|
if (auto const f = dyn_cast<FunctionDecl>(*d)) {
|
|
if (f->getCanonicalDecl() != canon) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
loplugin::Plugin::Registration<RedundantCast> X("redundantcast", true);
|
|
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|