/* -*- 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/. */ #include #include #include #include #include #include "compat.hxx" #include "plugin.hxx" // // We don't like using C-style casts in C++ code. Similarly, warn about function-style casts (which // are semantically equivalent to C-style casts) that are not semantically equivalent to static_cast // and should rather be written as const_cast or reinterpret_cast. // namespace { bool areSimilar(QualType type1, QualType type2) { auto t1 = type1.getCanonicalType().getTypePtr(); auto t2 = type2.getCanonicalType().getTypePtr(); for (;;) { if (t1->isPointerType()) { if (!t2->isPointerType()) { return false; } auto t1a = t1->getAs(); auto t2a = t2->getAs(); t1 = t1a->getPointeeType().getTypePtr(); t2 = t2a->getPointeeType().getTypePtr(); } else if (t1->isMemberPointerType()) { if (!t2->isMemberPointerType()) { return false; } auto t1a = t1->getAs(); auto t2a = t2->getAs(); if (t1a->getClass()->getCanonicalTypeInternal() != t2a->getClass()->getCanonicalTypeInternal()) { return false; } t1 = t1a->getPointeeType().getTypePtr(); t2 = t2a->getPointeeType().getTypePtr(); } else if (t1->isConstantArrayType()) { if (!t2->isConstantArrayType()) { return false; } auto t1a = static_cast( t1->getAsArrayTypeUnsafe()); auto t2a = static_cast( t2->getAsArrayTypeUnsafe()); if (t1a->getSize() != t2a->getSize()) { return false; } t1 = t1a->getElementType().getTypePtr(); t2 = t2a->getElementType().getTypePtr(); } else if (t1->isIncompleteArrayType()) { if (!t2->isIncompleteArrayType()) { return false; } auto t1a = static_cast( t1->getAsArrayTypeUnsafe()); auto t2a = static_cast( t2->getAsArrayTypeUnsafe()); t1 = t1a->getElementType().getTypePtr(); t2 = t2a->getElementType().getTypePtr(); } else { return false; } if (t1 == t2) { return true; } } } QualType resolvePointers(QualType type) { while (type->isPointerType()) { type = type->getAs()->getPointeeType(); } return type; } bool isLiteralLike(Expr const * expr) { expr = expr->IgnoreParenImpCasts(); if (isa(expr) || isa(expr) || isa(expr) || isa(expr) || isa(expr) || isa(expr) || isa(expr)) { return true; } if (auto const e = dyn_cast(expr)) { auto const d = e->getDecl(); if (isa(d)) { return true; } if (auto const v = dyn_cast(d)) { if (d->getType().isConstQualified()) { if (auto const init = v->getAnyInitializer()) { return isLiteralLike(init); } } } return false; } if (auto const e = dyn_cast(expr)) { auto const k = e->getKind(); return k == UETT_SizeOf || k == UETT_AlignOf; } if (auto const e = dyn_cast(expr)) { auto const k = e->getOpcode(); if (k == UO_Plus || k == UO_Minus || k == UO_Not || k == UO_LNot) { return isLiteralLike(e->getSubExpr()); } return false; } if (auto const e = dyn_cast(expr)) { auto const k = e->getOpcode(); if (k == BO_Mul || k == BO_Div || k == BO_Rem || k == BO_Add || k == BO_Sub || k == BO_Shl || k == BO_Shr || k == BO_And || k == BO_Xor || k == BO_Or) { return isLiteralLike(e->getLHS()) && isLiteralLike(e->getRHS()); } return false; } if (auto const e = dyn_cast(expr)) { auto const t = e->getTypeAsWritten(); return (t->isArithmeticType() || t->isEnumeralType()) && isLiteralLike(e->getSubExprAsWritten()); } return false; } bool canBeUsedForFunctionalCast(TypeSourceInfo const * info) { // Must be or , lets approximate that here: assert(info != nullptr); auto const type = info->getType(); if (type.hasLocalQualifiers()) { return false; } if (auto const t = dyn_cast(type)) { if (!(t->isInteger() || t->isFloatingPoint())) { return false; } auto const loc = info->getTypeLoc().castAs(); return (int(loc.hasWrittenSignSpec()) + int(loc.hasWrittenWidthSpec()) + int(loc.hasWrittenTypeSpec())) == 1; } if (isa(type) || isa(type) || isa(type) || isa(type) || isa(type)) { return true; } if (auto const t = dyn_cast(type)) { return t->getKeyword() == compat::ElaboratedTypeKeyword::None; } return false; } class CStyleCast: public loplugin::FilteringRewritePlugin { public: explicit CStyleCast(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 TraverseLinkageSpecDecl(LinkageSpecDecl * decl); bool VisitCStyleCastExpr(const CStyleCastExpr * expr); bool VisitCXXFunctionalCastExpr(CXXFunctionalCastExpr const * expr); private: bool isConstCast(QualType from, QualType to); bool isFromCIncludeFile(SourceLocation spellingLocation) const; bool isSharedCAndCppCode(SourceLocation location) const; bool isLastTokenOfImmediateMacroBodyExpansion( SourceLocation loc, SourceLocation * macroEnd = nullptr) const; bool rewriteArithmeticCast(CStyleCastExpr const * expr, char const ** replacement); void reportCast(ExplicitCastExpr const * expr, char const * performsHint); unsigned int externCContexts_ = 0; std::set rewritten_; // needed when rewriting in macros, in general to avoid "double code replacement, possible // plugin error" warnings, and in particular to avoid adding multiple sets of parens around // sub-exprs std::set rewrittenSubExprs_; }; const char * recommendedFix(clang::CastKind ck) { switch(ck) { case CK_IntegralToPointer: return "reinterpret_cast"; case CK_PointerToIntegral: return "reinterpret_cast"; case CK_BaseToDerived: return "static_cast"; default: return nullptr; } } bool CStyleCast::TraverseLinkageSpecDecl(LinkageSpecDecl * decl) { assert(externCContexts_ != std::numeric_limits::max()); //TODO ++externCContexts_; bool ret = RecursiveASTVisitor::TraverseLinkageSpecDecl(decl); assert(externCContexts_ != 0); --externCContexts_; return ret; } bool CStyleCast::VisitCStyleCastExpr(const CStyleCastExpr * expr) { if (ignoreLocation(expr)) { return true; } // casting to void is typically used when a parameter or field is only used in // debug mode, and we want to eliminate an "unused" warning if( expr->getCastKind() == CK_ToVoid ) { return true; } if (isSharedCAndCppCode(expr->getBeginLoc())) { return true; } char const * perf = nullptr; if( expr->getCastKind() == CK_IntegralCast ) { if (rewriteArithmeticCast(expr, &perf)) { return true; } } else if( expr->getCastKind() == CK_NoOp ) { if (!((expr->getSubExpr()->getType()->isPointerType() && expr->getType()->isPointerType()) || expr->getTypeAsWritten()->isReferenceType())) { if (rewriteArithmeticCast(expr, &perf)) { return true; } } if (isConstCast( expr->getSubExprAsWritten()->getType(), expr->getTypeAsWritten())) { perf = "const_cast"; } } reportCast(expr, perf); return true; } bool CStyleCast::VisitCXXFunctionalCastExpr(CXXFunctionalCastExpr const * expr) { if (ignoreLocation(expr)) { return true; } char const * perf = nullptr; switch (expr->getCastKind()) { case CK_ConstructorConversion: case CK_Dependent: //TODO: really filter out all of these? case CK_IntegralCast: case CK_IntegralToBoolean: case CK_ToVoid: return true; case CK_NoOp: if (isConstCast( expr->getSubExprAsWritten()->getType(), expr->getTypeAsWritten())) { perf = "const_cast"; break; } return true; //TODO: really filter out all of these? default: break; } reportCast(expr, perf); return true; } bool CStyleCast::isConstCast(QualType from, QualType to) { if (to->isReferenceType() && to->getAs()->getPointeeType()->isObjectType()) { if (!from->isObjectType()) { return false; } from = compiler.getASTContext().getPointerType(from); to = compiler.getASTContext().getPointerType( to->getAs()->getPointeeType()); } else { if (from->isArrayType()) { from = compiler.getASTContext().getPointerType( from->getAsArrayTypeUnsafe()->getElementType()); } else if (from->isFunctionType()) { compiler.getASTContext().getPointerType(from); } } return areSimilar(from, to); } bool CStyleCast::isFromCIncludeFile(SourceLocation spellingLocation) const { return !compiler.getSourceManager().isInMainFile(spellingLocation) && compat::ends_with( StringRef(compiler.getSourceManager().getPresumedLoc(spellingLocation).getFilename()), ".h"); } bool CStyleCast::isSharedCAndCppCode(SourceLocation location) const { while (compiler.getSourceManager().isMacroArgExpansion(location)) { location = compiler.getSourceManager().getImmediateMacroCallerLoc( location); } // Assume that code is intended to be shared between C and C++ if it comes // from an include file ending in .h, and is either in an extern "C" context // or the body of a macro definition: return isFromCIncludeFile(compiler.getSourceManager().getSpellingLoc(location)) && (externCContexts_ != 0 || compiler.getSourceManager().isMacroBodyExpansion(location)); } bool CStyleCast::isLastTokenOfImmediateMacroBodyExpansion( SourceLocation loc, SourceLocation * macroEnd) const { assert(compiler.getSourceManager().isMacroBodyExpansion(loc)); auto const spell = compiler.getSourceManager().getSpellingLoc(loc); auto name = Lexer::getImmediateMacroName( loc, compiler.getSourceManager(), compiler.getLangOpts()); while (compat::starts_with(name, "\\\n")) { name = name.drop_front(2); while (!name.empty() && (name.front() == ' ' || name.front() == '\t' || name.front() == '\n' || name.front() == '\v' || name.front() == '\f')) { name = name.drop_front(1); } } auto const MI = (compiler.getPreprocessor().getMacroDefinitionAtLoc( &compiler.getASTContext().Idents.get(name), spell) .getMacroInfo()); assert(MI != nullptr); if (spell == MI->getDefinitionEndLoc()) { if (macroEnd != nullptr) { *macroEnd = compat::getImmediateExpansionRange(compiler.getSourceManager(), loc).second; } return true; } return false; } bool CStyleCast::rewriteArithmeticCast(CStyleCastExpr const * expr, char const ** replacement) { assert(replacement != nullptr); auto const sub = expr->getSubExprAsWritten(); auto const functional = isLiteralLike(sub) && canBeUsedForFunctionalCast(expr->getTypeInfoAsWritten()); *replacement = functional ? "functional cast" : "static_cast"; if (rewriter == nullptr) { return false; } // Doing modifications for a chain of C-style casts as in // // (foo)(bar)(baz)x // // leads to unpredictable results, so only rewrite them one at a time, starting with the // outermost: if (auto const e = dyn_cast(sub)) { rewrittenSubExprs_.insert(e); } if (rewrittenSubExprs_.find(expr) != rewrittenSubExprs_.end()) { return false; } // Two or four ranges to replace: // First is the CStyleCast's LParen, plus following whitespace, replaced with either "" or // "static_cast<". (TODO: insert space before "static_cast<" when converting "else(int)...".) // Second is the CStyleCast's RParen, plus preceding and following whitespace, replaced with // either "" or ">". // If the sub expr is not a ParenExpr, third is the sub expr's begin, inserting "(", and fourth // is the sub expr's end, inserting ")". // (The reason the second and third are not combined is in case there's a comment between them.) auto firstBegin = expr->getLParenLoc(); auto secondBegin = expr->getRParenLoc(); while (compiler.getSourceManager().isMacroArgExpansion(firstBegin) && compiler.getSourceManager().isMacroArgExpansion(secondBegin) && (compat::getImmediateExpansionRange(compiler.getSourceManager(), firstBegin) == compat::getImmediateExpansionRange(compiler.getSourceManager(), secondBegin))) { firstBegin = compiler.getSourceManager().getImmediateSpellingLoc(firstBegin); secondBegin = compiler.getSourceManager().getImmediateSpellingLoc(secondBegin); } if (compiler.getSourceManager().isMacroBodyExpansion(firstBegin) && compiler.getSourceManager().isMacroBodyExpansion(secondBegin) && (compiler.getSourceManager().getImmediateMacroCallerLoc(firstBegin) == compiler.getSourceManager().getImmediateMacroCallerLoc(secondBegin))) { firstBegin = compiler.getSourceManager().getSpellingLoc(firstBegin); secondBegin = compiler.getSourceManager().getSpellingLoc(secondBegin); } auto third = sub->getBeginLoc(); auto fourth = sub->getEndLoc(); bool macro = false; // Ensure that // // #define FOO(x) (int)x // FOO(y) // // is changed to // // #define FOO(x) static_cast(x) // FOO(y) // // instead of // // #define FOO(x) static_castx // FOO((y)) while (compiler.getSourceManager().isMacroArgExpansion(third) && compiler.getSourceManager().isMacroArgExpansion(fourth) && (compat::getImmediateExpansionRange(compiler.getSourceManager(), third) == compat::getImmediateExpansionRange(compiler.getSourceManager(), fourth)) && compiler.getSourceManager().isAtStartOfImmediateMacroExpansion(third)) //TODO: check fourth is at end of immediate macro expansion, but // SourceManager::isAtEndOfImmediateMacroExpansion requires a location pointing at the // character end of the last token { auto const range = compat::getImmediateExpansionRange(compiler.getSourceManager(), third); third = range.first; fourth = range.second; macro = true; assert(third.isValid()); } while (compiler.getSourceManager().isMacroArgExpansion(third) && compiler.getSourceManager().isMacroArgExpansion(fourth) && (compat::getImmediateExpansionRange(compiler.getSourceManager(), third) == compat::getImmediateExpansionRange(compiler.getSourceManager(), fourth))) { third = compiler.getSourceManager().getImmediateSpellingLoc(third); fourth = compiler.getSourceManager().getImmediateSpellingLoc(fourth); } if (isa(sub)) { // Ensure that with // // #define FOO (x) // // a cast like // // (int) FOO // // is changed to // // static_cast(FOO) // // instead of // // static_castFOO for (;; macro = true) { if (!(compiler.getSourceManager().isMacroBodyExpansion(third) && compiler.getSourceManager().isMacroBodyExpansion(fourth) && (compiler.getSourceManager().getImmediateMacroCallerLoc(third) == compiler.getSourceManager().getImmediateMacroCallerLoc(fourth)) && compiler.getSourceManager().isAtStartOfImmediateMacroExpansion(third) && isLastTokenOfImmediateMacroBodyExpansion(fourth))) { if (!macro) { third = fourth = SourceLocation(); } break; } auto const range = compat::getImmediateExpansionRange( compiler.getSourceManager(), third); third = range.first; fourth = range.second; assert(third.isValid()); } if (third.isValid() && compiler.getSourceManager().isMacroBodyExpansion(third) && compiler.getSourceManager().isMacroBodyExpansion(fourth) && (compiler.getSourceManager().getImmediateMacroCallerLoc(third) == compiler.getSourceManager().getImmediateMacroCallerLoc(fourth))) { third = compiler.getSourceManager().getSpellingLoc(third); fourth = compiler.getSourceManager().getSpellingLoc(fourth); assert(third.isValid()); } } else { // Ensure that a cast like // // (int)LONG_MAX // // (where LONG_MAX expands to __LONG_MAX__, which in turn is a built-in expanding to a value // like 9223372036854775807L) is changed to // // int(LONG_MAX) // // instead of trying to add the parentheses to the built-in __LONG_MAX__ definition: for (;;) { if (!(compiler.getSourceManager().isMacroBodyExpansion(third) && compiler.getSourceManager().isMacroBodyExpansion(fourth) && (compiler.getSourceManager().getImmediateMacroCallerLoc(third) == compiler.getSourceManager().getImmediateMacroCallerLoc(fourth)) && compiler.getSourceManager().isAtStartOfImmediateMacroExpansion(third))) // TODO: check that fourth is at end of immediate macro expansion (but // SourceManager::isAtEndOfImmediateMacroExpansion wants a location pointing at the // character end) { break; } auto const range = compat::getImmediateExpansionRange( compiler.getSourceManager(), third); third = range.first; fourth = range.second; } // ...and additionally asymmetrically unwind macros only at the start or end, for code like // // (long)ubidi_getVisualIndex(...) // // (in editeng/source/editeng/impedit2.cxx) where ubidi_getVisualIndex is an object-like // macro, or // // #define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c) // // (in hwpfilter/source/lexer.cxx): if (!fourth.isMacroID()) { while (compiler.getSourceManager().isMacroBodyExpansion(third) && compiler.getSourceManager().isAtStartOfImmediateMacroExpansion(third, &third)) {} } else if (compiler.getSourceManager().isMacroBodyExpansion(fourth)) { while (compiler.getSourceManager().isMacroArgExpansion(third) && compiler.getSourceManager().isAtStartOfImmediateMacroExpansion(third, &third)) {} } if (!third.isMacroID()) { while (compiler.getSourceManager().isMacroBodyExpansion(fourth) && isLastTokenOfImmediateMacroBodyExpansion(fourth, &fourth)) {} } else if (compiler.getSourceManager().isMacroBodyExpansion(third)) { while (compiler.getSourceManager().isMacroArgExpansion(fourth, &fourth)) {} } if (compiler.getSourceManager().isMacroBodyExpansion(third) && compiler.getSourceManager().isMacroBodyExpansion(fourth) && (compiler.getSourceManager().getImmediateMacroCallerLoc(third) == compiler.getSourceManager().getImmediateMacroCallerLoc(fourth))) { third = compiler.getSourceManager().getSpellingLoc(third); fourth = compiler.getSourceManager().getSpellingLoc(fourth); } assert(third.isValid()); } if (firstBegin.isMacroID() || secondBegin.isMacroID() || (third.isValid() && third.isMacroID()) || (fourth.isValid() && fourth.isMacroID())) { if (isDebugMode()) { report( DiagnosticsEngine::Fatal, "TODO: cannot rewrite C-style cast in macro, needs investigation", expr->getExprLoc()) << expr->getSourceRange(); } return false; } unsigned firstLen = Lexer::MeasureTokenLength( firstBegin, compiler.getSourceManager(), compiler.getLangOpts()); for (auto l = firstBegin.getLocWithOffset(std::max(firstLen, 1));; l = l.getLocWithOffset(1)) { unsigned n = Lexer::MeasureTokenLength( l, compiler.getSourceManager(), compiler.getLangOpts()); if (n != 0) { break; } ++firstLen; } unsigned secondLen = Lexer::MeasureTokenLength( secondBegin, compiler.getSourceManager(), compiler.getLangOpts()); for (auto l = secondBegin.getLocWithOffset(std::max(secondLen, 1));; l = l.getLocWithOffset(1)) { unsigned n = Lexer::MeasureTokenLength( l, compiler.getSourceManager(), compiler.getLangOpts()); if (n != 0) { break; } ++secondLen; } for (;;) { auto l = secondBegin.getLocWithOffset(-1); auto const c = compiler.getSourceManager().getCharacterData(l)[0]; if (c == '\n') { if (compiler.getSourceManager().getCharacterData(l.getLocWithOffset(-1))[0] == '\\') { break; } } else if (!(c == ' ' || c == '\t' || c == '\v' || c == '\f')) { break; } secondBegin = l; ++secondLen; } if (rewritten_.insert(firstBegin).second) { if (!replaceText(firstBegin, firstLen, functional ? "" : "static_cast<")) { if (isDebugMode()) { report( DiagnosticsEngine::Fatal, "TODO: cannot rewrite #1, needs investigation", firstBegin); report( DiagnosticsEngine::Note, "when rewriting this C-style cast", expr->getExprLoc()) << expr->getSourceRange(); } return false; } if (!replaceText(secondBegin, secondLen, functional ? "" : ">")) { //TODO: roll back if (isDebugMode()) { report( DiagnosticsEngine::Fatal, "TODO: cannot rewrite #2, needs investigation", secondBegin); report( DiagnosticsEngine::Note, "when rewriting this C-style cast", expr->getExprLoc()) << expr->getSourceRange(); } return false; } } if (third.isValid()) { if (rewritten_.insert(third).second) { if (!insertTextBefore(third, "(")) { //TODO: roll back if (isDebugMode()) { report( DiagnosticsEngine::Fatal, "TODO: cannot rewrite #3, needs investigation", third); report( DiagnosticsEngine::Note, "when rewriting this C-style cast", expr->getExprLoc()) << expr->getSourceRange(); } return false; } if (!insertTextAfterToken(fourth, ")")) { //TODO: roll back if (isDebugMode()) { report( DiagnosticsEngine::Fatal, "TODO: cannot rewrite #4, needs investigation", third); report( DiagnosticsEngine::Note, "when rewriting this C-style cast", expr->getExprLoc()) << expr->getSourceRange(); } return false; } } } return true; } void CStyleCast::reportCast(ExplicitCastExpr const * expr, char const * performsHint) { std::string incompFrom; std::string incompTo; if( expr->getCastKind() == CK_BitCast ) { if (resolvePointers(expr->getSubExprAsWritten()->getType()) ->isIncompleteType()) { incompFrom = "incomplete "; } if (resolvePointers(expr->getType())->isIncompleteType()) { incompTo = "incomplete "; } } if (performsHint == nullptr) { performsHint = recommendedFix(expr->getCastKind()); } std::string performs; if (performsHint != nullptr) { performs = std::string(" (performs: ") + performsHint + ")"; } report( DiagnosticsEngine::Warning, "%select{C|Function}0-style cast from %1%2 to %3%4%5 (%6)", expr->getSourceRange().getBegin()) << isa(expr) << incompFrom << expr->getSubExprAsWritten()->getType() << incompTo << expr->getTypeAsWritten() << performs << expr->getCastKindName() << expr->getSourceRange(); } loplugin::Plugin::Registration< CStyleCast > X("cstylecast", true); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */