summaryrefslogtreecommitdiff
path: root/compilerplugins
diff options
context:
space:
mode:
authorStephan Bergmann <sbergman@redhat.com>2018-01-10 15:55:22 +0100
committerStephan Bergmann <sbergman@redhat.com>2018-01-12 20:32:32 +0100
commitcab0427cadddb3aaf1349c66f2fa13a4234ba4b2 (patch)
tree80505fe67f1c724c1fe6a2d9b7e718bd148542ef /compilerplugins
parent8288796fe49d61dbfa46ac29c305e95b4b78e72e (diff)
Enable loplugin:cstylecast for some more cases
...mostly of C-style casts among arithmetic types, and automatically rewrite those into either static_cast or a functional cast (which should have identical semantics, but where the latter probably looks better for simple cases like casting a literal to a specific type, as in "sal_Int32(0)" vs. "static_cast<sal_Int32>(0)"). The main benefit of reducing the amount of C-style casts across the code base further is so that other plugins (that have not been taught about the complex semantics of C-style cast) can pick those up (cf. the various recent "loplugin:redundantcast" commits, which address those findings after this improved loplugin:cstylecast has been run). Also, I found some places where a C-style cast has probably been applied only to the first part of a larger expression in error (because it's easy to forget parentheses in cases like "(sal_uInt16)VOPT_CLIPMARKS+1"); I'll follow up on those individually. The improved loplugin:cstylecast is careful to output either "(performs: static_cast)" or "(performs: functional cast)", so that compilerplugins/clang/test/cstylecast.cxx can check that the plugin would automatically rewrite to one or the other form. To allow fully-automatic rewriting, this also required loplugin:unnecessaryparen to become a rewriting plugin, at least for the parens-around-cast case (where "((foo)bar)" first gets rewritten to "(static_cast<foo>(bar))", then to "static_cast<foo>(bar)". Rewriting could probably be added to other cases of loplugin:unnecessaryparen in the future, too. (The final version of this patch would even have been able to cope with 361dd2576a09fbda83f3ce9a26ecb590c38f74e3 "Replace some C-style casts in ugly macros with static_cast", so that manual change would not have been necessary after all.) Change-Id: Icd7e319cc38eb58262fcbf7643d177ac9ea0220a Reviewed-on: https://gerrit.libreoffice.org/47798 Tested-by: Jenkins <ci@libreoffice.org> Reviewed-by: Stephan Bergmann <sbergman@redhat.com>
Diffstat (limited to 'compilerplugins')
-rw-r--r--compilerplugins/clang/cstylecast.cxx440
-rw-r--r--compilerplugins/clang/test/cstylecast.cxx63
-rw-r--r--compilerplugins/clang/unnecessaryparen.cxx104
3 files changed, 594 insertions, 13 deletions
diff --git a/compilerplugins/clang/cstylecast.cxx b/compilerplugins/clang/cstylecast.cxx
index bf8e2fb00809..f09ce81a2987 100644
--- a/compilerplugins/clang/cstylecast.cxx
+++ b/compilerplugins/clang/cstylecast.cxx
@@ -7,8 +7,10 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
+#include <algorithm>
#include <cassert>
#include <limits>
+#include <set>
#include <string>
#include "plugin.hxx"
@@ -82,11 +84,89 @@ QualType resolvePointers(QualType type) {
return type;
}
+bool isLiteralLike(Expr const * expr) {
+ expr = expr->IgnoreParenImpCasts();
+ if (isa<IntegerLiteral>(expr) || isa<CharacterLiteral>(expr) || isa<FloatingLiteral>(expr)
+ || isa<ImaginaryLiteral>(expr) || isa<CXXBoolLiteralExpr>(expr)
+ || isa<CXXNullPtrLiteralExpr>(expr) || isa<ObjCBoolLiteralExpr>(expr))
+ {
+ return true;
+ }
+ if (auto const e = dyn_cast<DeclRefExpr>(expr)) {
+ auto const d = e->getDecl();
+ if (isa<EnumConstantDecl>(d)) {
+ return true;
+ }
+ if (auto const v = dyn_cast<VarDecl>(d)) {
+ if (d->getType().isConstQualified()) {
+ if (auto const init = v->getAnyInitializer()) {
+ return isLiteralLike(init);
+ }
+ }
+ }
+ return false;
+ }
+ if (auto const e = dyn_cast<UnaryExprOrTypeTraitExpr>(expr)) {
+ auto const k = e->getKind();
+ return k == UETT_SizeOf || k == UETT_AlignOf;
+ }
+ if (auto const e = dyn_cast<UnaryOperator>(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<BinaryOperator>(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<ExplicitCastExpr>(expr)) {
+ auto const t = e->getTypeAsWritten();
+ return (t->isArithmeticType() || t->isEnumeralType())
+ && isLiteralLike(e->getSubExprAsWritten());
+ }
+ return false;
+}
+
+bool canBeUsedForFunctionalCast(TypeSourceInfo const * info) {
+ // Must be <simple-type-specifier> or <typename-specifier>, lets approximate that here:
+ assert(info != nullptr);
+ auto const type = info->getType();
+ if (type.hasLocalQualifiers()) {
+ return false;
+ }
+ if (auto const t = dyn_cast<BuiltinType>(type)) {
+ if (!(t->isInteger() || t->isFloatingPoint())) {
+ return false;
+ }
+ auto const loc = info->getTypeLoc().castAs<BuiltinTypeLoc>();
+ return
+ (int(loc.hasWrittenSignSpec()) + int(loc.hasWrittenWidthSpec())
+ + int(loc.hasWrittenTypeSpec()))
+ == 1;
+ }
+ if (isa<TagType>(type) || isa<TemplateTypeParmType>(type) || isa<AutoType>(type)
+ || isa<DecltypeType>(type) || isa<TypedefType>(type))
+ {
+ return true;
+ }
+ if (auto const t = dyn_cast<ElaboratedType>(type)) {
+ return t->getKeyword() == ETK_None;
+ }
+ return false;
+}
+
class CStyleCast:
- public RecursiveASTVisitor<CStyleCast>, public loplugin::Plugin
+ public RecursiveASTVisitor<CStyleCast>, public loplugin::RewritePlugin
{
public:
- explicit CStyleCast(loplugin::InstantiationData const & data): Plugin(data)
+ explicit CStyleCast(loplugin::InstantiationData const & data): RewritePlugin(data)
{}
virtual void run() override {
@@ -95,6 +175,12 @@ public:
}
}
+ 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);
@@ -106,7 +192,17 @@ private:
bool isSharedCAndCppCode(SourceLocation location) const;
+ bool isLastTokenOfImmediateMacroBodyExpansion(
+ SourceLocation loc, SourceLocation * macroEnd = nullptr) const;
+
+ bool rewriteArithmeticCast(CStyleCastExpr const * expr, char const ** replacement);
+
unsigned int externCContexts_ = 0;
+ std::set<SourceLocation> 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<CStyleCastExpr const *> rewrittenSubExprs_;
};
const char * recommendedFix(clang::CastKind ck) {
@@ -141,13 +237,18 @@ bool CStyleCast::VisitCStyleCastExpr(const CStyleCastExpr * expr) {
if( expr->getCastKind() == CK_IntegralCast ) {
return true;
}
+ if (isSharedCAndCppCode(expr->getLocStart())) {
+ return true;
+ }
char const * perf = nullptr;
if( expr->getCastKind() == CK_NoOp ) {
if (!((expr->getSubExpr()->getType()->isPointerType()
&& expr->getType()->isPointerType())
|| expr->getTypeAsWritten()->isReferenceType()))
{
- return true;
+ if (rewriteArithmeticCast(expr, &perf)) {
+ return true;
+ }
}
if (isConstCast(
expr->getSubExprAsWritten()->getType(),
@@ -156,9 +257,6 @@ bool CStyleCast::VisitCStyleCastExpr(const CStyleCastExpr * expr) {
perf = "const_cast";
}
}
- if (isSharedCAndCppCode(expr->getLocStart())) {
- return true;
- }
std::string incompFrom;
std::string incompTo;
if( expr->getCastKind() == CK_BitCast ) {
@@ -231,7 +329,335 @@ bool CStyleCast::isSharedCAndCppCode(SourceLocation location) const {
|| compiler.getSourceManager().isMacroBodyExpansion(location));
}
-loplugin::Plugin::Registration< CStyleCast > X("cstylecast");
+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 (name.startswith("\\\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 = compiler.getSourceManager().getImmediateExpansionRange(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<CStyleCastExpr>(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<". (TOOD: 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)
+ && (compiler.getSourceManager().getImmediateExpansionRange(firstBegin)
+ == compiler.getSourceManager().getImmediateExpansionRange(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->getLocStart();
+ auto fourth = sub->getLocEnd();
+ bool macro = false;
+ // Ensure that
+ //
+ // #define FOO(x) (int)x
+ // FOO(y)
+ //
+ // is changed to
+ //
+ // #define FOO(x) static_cast<int>(x)
+ // FOO(y)
+ //
+ // instead of
+ //
+ // #define FOO(x) static_cast<int>x
+ // FOO((y))
+ while (compiler.getSourceManager().isMacroArgExpansion(third)
+ && compiler.getSourceManager().isMacroArgExpansion(fourth)
+ && (compiler.getSourceManager().getImmediateExpansionRange(third)
+ == compiler.getSourceManager().getImmediateExpansionRange(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 = compiler.getSourceManager().getImmediateExpansionRange(third);
+ third = range.first;
+ fourth = range.second;
+ macro = true;
+ assert(third.isValid());
+ }
+ while (compiler.getSourceManager().isMacroArgExpansion(third)
+ && compiler.getSourceManager().isMacroArgExpansion(fourth)
+ && (compiler.getSourceManager().getImmediateExpansionRange(third)
+ == compiler.getSourceManager().getImmediateExpansionRange(fourth)))
+ {
+ third = compiler.getSourceManager().getImmediateSpellingLoc(third);
+ fourth = compiler.getSourceManager().getImmediateSpellingLoc(fourth);
+ }
+ if (isa<ParenExpr>(sub)) {
+ // Ensure that with
+ //
+ // #define FOO (x)
+ //
+ // a cast like
+ //
+ // (int) FOO
+ //
+ // is changed to
+ //
+ // static_cast<int>(FOO)
+ //
+ // instead of
+ //
+ // static_cast<int>FOO
+ 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 = compiler.getSourceManager().getImmediateExpansionRange(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 = compiler.getSourceManager().getImmediateExpansionRange(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))
+ {}
+ }
+ 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<unsigned>(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<unsigned>(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_.find(firstBegin) == rewritten_.end()) {
+ rewritten_.insert(firstBegin);
+ 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_.find(third) == rewritten_.end()) {
+ rewritten_.insert(third);
+ 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;
+}
+
+loplugin::Plugin::Registration< CStyleCast > X("cstylecast", true);
}
diff --git a/compilerplugins/clang/test/cstylecast.cxx b/compilerplugins/clang/test/cstylecast.cxx
new file mode 100644
index 000000000000..c272005650ff
--- /dev/null
+++ b/compilerplugins/clang/test/cstylecast.cxx
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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/.
+ */
+
+namespace N
+{
+enum E
+{
+ E1
+};
+
+using T = unsigned int;
+}
+
+static const int C
+ = (int)0; // expected-error {{C-style cast from 'int' to 'int' (performs: functional cast) (NoOp) [loplugin:cstylecast]}}
+
+int main()
+{
+ constexpr int c1 = 0;
+ int const c2 = 0;
+ int n
+ = (int)0; // expected-error {{C-style cast from 'int' to 'int' (performs: functional cast) (NoOp) [loplugin:cstylecast]}}
+ n = (signed int)0; // expected-error {{C-style cast from 'int' to 'int' (performs: static_cast) (NoOp) [loplugin:cstylecast]}}
+ n = (int)~0; // expected-error {{C-style cast from 'int' to 'int' (performs: functional cast) (NoOp) [loplugin:cstylecast]}}
+ n = (int)-0; // expected-error {{C-style cast from 'int' to 'int' (performs: functional cast) (NoOp) [loplugin:cstylecast]}}
+ n = (int)+0; // expected-error {{C-style cast from 'int' to 'int' (performs: functional cast) (NoOp) [loplugin:cstylecast]}}
+ n = (int)!0; // expected-error {{C-style cast from 'bool' to 'int' (performs: functional cast) (NoOp) [loplugin:cstylecast]}}
+ n = (int) // expected-error {{C-style cast from 'int' to 'int' (performs: functional cast) (NoOp) [loplugin:cstylecast]}}
+ (0 << 0);
+ n = (int) // expected-error {{C-style cast from 'const int' to 'int' (performs: functional cast) (NoOp) [loplugin:cstylecast]}}
+ c1;
+ n = (int) // expected-error {{C-style cast from 'const int' to 'int' (performs: functional cast) (NoOp) [loplugin:cstylecast]}}
+ c2;
+ n = (int) // expected-error {{C-style cast from 'const int' to 'int' (performs: functional cast) (NoOp) [loplugin:cstylecast]}}
+ C;
+ n = (int) // expected-error {{C-style cast from 'N::E' to 'int' (performs: functional cast) (NoOp) [loplugin:cstylecast]}}
+ N::E1;
+ n = (N::E) // expected-error {{C-style cast from 'N::E' to 'N::E' (performs: functional cast) (NoOp) [loplugin:cstylecast]}}
+ N::E1;
+ n = (enum // expected-error {{C-style cast from 'N::E' to 'enum N::E' (performs: static_cast) (NoOp) [loplugin:cstylecast]}}
+ N::E)N::E1;
+ n = (N::T)0; // expected-error {{C-style cast from 'int' to 'N::T' (aka 'unsigned int') (performs: functional cast) (NoOp) [loplugin:cstylecast]}}
+ n = (int) // expected-error-re {{C-style cast from {{.*}} to 'int' (performs: functional cast) (NoOp) [loplugin:cstylecast]}}
+ sizeof(int);
+ n = (int) // expected-error {{C-style cast from 'int' to 'int' (performs: static_cast) (NoOp) [loplugin:cstylecast]}}
+ n;
+ n = (int)~n; // expected-error {{C-style cast from 'int' to 'int' (performs: static_cast) (NoOp) [loplugin:cstylecast]}}
+ n = (int)-n; // expected-error {{C-style cast from 'int' to 'int' (performs: static_cast) (NoOp) [loplugin:cstylecast]}}
+ n = (int)+n; // expected-error {{C-style cast from 'int' to 'int' (performs: static_cast) (NoOp) [loplugin:cstylecast]}}
+ n = (int)!n; // expected-error {{C-style cast from 'bool' to 'int' (performs: static_cast) (NoOp) [loplugin:cstylecast]}}
+ n = (int) // expected-error {{C-style cast from 'int' to 'int' (performs: static_cast) (NoOp) [loplugin:cstylecast]}}
+ (0 << n);
+ n = (double)0; // expected-error {{C-style cast from 'int' to 'double' (performs: functional cast) (NoOp) [loplugin:cstylecast]}}
+ (void)n;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/compilerplugins/clang/unnecessaryparen.cxx b/compilerplugins/clang/unnecessaryparen.cxx
index ba6c62a78e43..d4ebd6ed0b14 100644
--- a/compilerplugins/clang/unnecessaryparen.cxx
+++ b/compilerplugins/clang/unnecessaryparen.cxx
@@ -59,11 +59,11 @@ Expr const * ignoreAllImplicit(Expr const * expr) {
}
class UnnecessaryParen:
- public RecursiveASTVisitor<UnnecessaryParen>, public loplugin::Plugin
+ public RecursiveASTVisitor<UnnecessaryParen>, public loplugin::RewritePlugin
{
public:
explicit UnnecessaryParen(loplugin::InstantiationData const & data):
- Plugin(data) {}
+ RewritePlugin(data) {}
virtual void run() override
{
@@ -103,6 +103,10 @@ private:
// SwNode::dumpAsXml (sw/source/core/docnode/node.cxx):
bool isPrecededBy_BAD_CAST(Expr const * expr);
+ bool badCombination(SourceLocation loc, int prevOffset, int nextOffset);
+
+ bool removeParens(ParenExpr const * expr);
+
std::unordered_set<ParenExpr const *> handled_;
};
@@ -200,10 +204,12 @@ bool UnnecessaryParen::VisitParenExpr(const ParenExpr* parenExpr)
}
}
} else if (isa<CXXNamedCastExpr>(subExpr)) {
- report(
- DiagnosticsEngine::Warning, "unnecessary parentheses around cast",
- parenExpr->getLocStart())
- << parenExpr->getSourceRange();
+ if (!removeParens(parenExpr)) {
+ report(
+ DiagnosticsEngine::Warning, "unnecessary parentheses around cast",
+ parenExpr->getLocStart())
+ << parenExpr->getSourceRange();
+ }
handled_.insert(parenExpr);
}
@@ -473,6 +479,92 @@ bool UnnecessaryParen::isPrecededBy_BAD_CAST(Expr const * expr) {
return std::string(p1, p2 - p1).find("BAD_CAST") != std::string::npos;
}
+namespace {
+
+bool badCombinationChar(char c) {
+ return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_'
+ || c == '+' || c == '-' || c == '\'' || c == '"';
+}
+
+}
+
+bool UnnecessaryParen::badCombination(SourceLocation loc, int prevOffset, int nextOffset) {
+ //TODO: check for start/end of file; take backslash-newline line concatentation into account
+ auto const c1
+ = compiler.getSourceManager().getCharacterData(loc.getLocWithOffset(prevOffset))[0];
+ auto const c2
+ = compiler.getSourceManager().getCharacterData(loc.getLocWithOffset(nextOffset))[0];
+ // An approximation of avoiding whatever combinations that would cause two ajacent tokens to be
+ // lexed differently, using, for now, letters (TODO: non-ASCII ones) and digits and '_'; '+' and
+ // '-' (to avoid ++, etc.); '\'' and '"' (to avoid u'x' or "foo"bar, etc.):
+ return badCombinationChar(c1) && badCombinationChar(c2);
+}
+
+bool UnnecessaryParen::removeParens(ParenExpr const * expr) {
+ if (rewriter == nullptr) {
+ return false;
+ }
+ auto const firstBegin = expr->getLocStart();
+ auto secondBegin = expr->getLocEnd();
+ if (firstBegin.isMacroID() || secondBegin.isMacroID()) {
+ return false;
+ }
+ unsigned firstLen = Lexer::MeasureTokenLength(
+ firstBegin, compiler.getSourceManager(), compiler.getLangOpts());
+ for (auto l = firstBegin.getLocWithOffset(std::max<unsigned>(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(-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 (!replaceText(firstBegin, firstLen, badCombination(firstBegin, -1, firstLen) ? " " : "")) {
+ if (isDebugMode()) {
+ report(
+ DiagnosticsEngine::Fatal,
+ "TODO: cannot rewrite opening parenthesis, needs investigation",
+ firstBegin);
+ report(
+ DiagnosticsEngine::Note, "when removing these parentheses", expr->getExprLoc())
+ << expr->getSourceRange();
+ }
+ return false;
+ }
+ if (!replaceText(secondBegin, secondLen, badCombination(secondBegin, -1, secondLen) ? " " : ""))
+ {
+ //TODO: roll back first change
+ if (isDebugMode()) {
+ report(
+ DiagnosticsEngine::Fatal,
+ "TODO: cannot rewrite closing parenthesis, needs investigation",
+ secondBegin);
+ report(
+ DiagnosticsEngine::Note, "when removing these parentheses", expr->getExprLoc())
+ << expr->getSourceRange();
+ }
+ return false;
+ }
+ return true;
+}
+
loplugin::Plugin::Registration< UnnecessaryParen > X("unnecessaryparen", true);
}