summaryrefslogtreecommitdiffstats
path: root/compilerplugins/clang/changetoolsgen.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'compilerplugins/clang/changetoolsgen.cxx')
-rw-r--r--compilerplugins/clang/changetoolsgen.cxx377
1 files changed, 377 insertions, 0 deletions
diff --git a/compilerplugins/clang/changetoolsgen.cxx b/compilerplugins/clang/changetoolsgen.cxx
new file mode 100644
index 000000000..b5eda7dbb
--- /dev/null
+++ b/compilerplugins/clang/changetoolsgen.cxx
@@ -0,0 +1,377 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * Based on LLVM/Clang.
+ *
+ * This file is distributed under the University of Illinois Open Source
+ * License. See LICENSE.TXT for details.
+ *
+ */
+
+#include "plugin.hxx"
+#include "check.hxx"
+#include <regex>
+
+/**
+ * Changes calls to tools::Rectangle/Point/Size methods that return a ref to instead call the setter methods.
+ *
+ * run as:
+ * make COMPILER_PLUGIN_TOOL=changetoolsgen UPDATE_FILES=all FORCE_COMPILE_ALL=1
+ * or
+ * make <module> COMPILER_PLUGIN_TOOL=changetoolsgen FORCE_COMPILE_ALL=1
+ */
+
+namespace
+{
+class ChangeToolsGen : public loplugin::FilteringRewritePlugin<ChangeToolsGen>
+{
+public:
+ explicit ChangeToolsGen(loplugin::InstantiationData const& data)
+ : FilteringRewritePlugin(data)
+ {
+ }
+ virtual void run() override;
+ bool VisitCXXMemberCallExpr(CXXMemberCallExpr const* call);
+
+private:
+ bool ChangeAssignment(Stmt const* parent, std::string const& methodName,
+ std::string const& setPrefix);
+ bool ChangeBinaryOperatorPlusMinus(BinaryOperator const* parent, CXXMemberCallExpr const* call,
+ std::string const& methodName);
+ bool ChangeBinaryOperatorOther(BinaryOperator const* parent, CXXMemberCallExpr const* call,
+ std::string const& methodName, std::string const& setPrefix);
+ bool ChangeUnaryOperator(UnaryOperator const* parent, CXXMemberCallExpr const* call,
+ std::string const& methodName);
+ std::string extractCode(SourceLocation startLoc, SourceLocation endLoc);
+};
+
+void ChangeToolsGen::run() { TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); }
+
+bool ChangeToolsGen::VisitCXXMemberCallExpr(CXXMemberCallExpr const* call)
+{
+ if (ignoreLocation(call))
+ return true;
+ const CXXMethodDecl* func = call->getMethodDecl();
+ if (!func)
+ return true;
+ if (func->isConst())
+ return true;
+ auto dc = loplugin::DeclCheck(func);
+ std::string methodName;
+ std::string setPrefix;
+ if (dc.Function("Top").Class("Rectangle").Namespace("tools").GlobalNamespace())
+ {
+ methodName = "Top";
+ setPrefix = "Set";
+ }
+ else if (dc.Function("Bottom").Class("Rectangle").Namespace("tools").GlobalNamespace())
+ {
+ methodName = "Bottom";
+ setPrefix = "Set";
+ }
+ else if (dc.Function("Left").Class("Rectangle").Namespace("tools").GlobalNamespace())
+ {
+ methodName = "Left";
+ setPrefix = "Set";
+ }
+ else if (dc.Function("Right").Class("Rectangle").Namespace("tools").GlobalNamespace())
+ {
+ methodName = "Right";
+ setPrefix = "Set";
+ }
+ else if (dc.Function("X").Class("Point").GlobalNamespace())
+ {
+ methodName = "X";
+ setPrefix = "set";
+ }
+ else if (dc.Function("Y").Class("Point").GlobalNamespace())
+ {
+ methodName = "Y";
+ setPrefix = "set";
+ }
+ else if (dc.Function("Width").Class("Size").GlobalNamespace())
+ {
+ methodName = "Width";
+ setPrefix = "set";
+ }
+ else if (dc.Function("Height").Class("Size").GlobalNamespace())
+ {
+ methodName = "Height";
+ setPrefix = "set";
+ }
+ else
+ return true;
+ if (!loplugin::TypeCheck(func->getReturnType()).LvalueReference())
+ return true;
+
+ auto parent = getParentStmt(call);
+ if (!parent)
+ return true;
+ if (auto unaryOp = dyn_cast<UnaryOperator>(parent))
+ {
+ if (!ChangeUnaryOperator(unaryOp, call, methodName))
+ report(DiagnosticsEngine::Warning, "Could not fix, unary", compat::getBeginLoc(call));
+ return true;
+ }
+ auto binaryOp = dyn_cast<BinaryOperator>(parent);
+ if (!binaryOp)
+ {
+ // normal getter
+ return true;
+ }
+ auto opcode = binaryOp->getOpcode();
+ if (opcode == BO_Assign)
+ {
+ // Check for assignments embedded inside other expressions
+ auto parent2 = getParentStmt(parent);
+ if (dyn_cast_or_null<ExprWithCleanups>(parent2))
+ parent2 = getParentStmt(parent2);
+ if (parent2 && isa<Expr>(parent2))
+ {
+ report(DiagnosticsEngine::Warning, "Could not fix, embedded assign",
+ compat::getBeginLoc(call));
+ return true;
+ }
+ // Check for
+ // X.Width() = X.Height() = 1;
+ if (auto rhs = dyn_cast<BinaryOperator>(binaryOp->getRHS()->IgnoreParenImpCasts()))
+ if (rhs->getOpcode() == BO_Assign)
+ {
+ report(DiagnosticsEngine::Warning, "Could not fix, double assign",
+ compat::getBeginLoc(call));
+ return true;
+ }
+ if (!ChangeAssignment(parent, methodName, setPrefix))
+ report(DiagnosticsEngine::Warning, "Could not fix, assign", compat::getBeginLoc(call));
+ return true;
+ }
+ if (opcode == BO_AddAssign || opcode == BO_SubAssign)
+ {
+ if (!ChangeBinaryOperatorPlusMinus(binaryOp, call, methodName))
+ report(DiagnosticsEngine::Warning, "Could not fix, assign-and-change",
+ compat::getBeginLoc(call));
+ return true;
+ }
+ else if (opcode == BO_RemAssign || opcode == BO_MulAssign || opcode == BO_DivAssign)
+ {
+ if (!ChangeBinaryOperatorOther(binaryOp, call, methodName, setPrefix))
+ report(DiagnosticsEngine::Warning, "Could not fix, assign-and-change",
+ compat::getBeginLoc(call));
+ return true;
+ }
+ else
+ assert(false);
+ return true;
+}
+
+bool ChangeToolsGen::ChangeAssignment(Stmt const* parent, std::string const& methodName,
+ std::string const& setPrefix)
+{
+ // Look for expressions like
+ // aRect.Left() = ...;
+ // and replace with
+ // aRect.SetLeft( ... );
+ SourceManager& SM = compiler.getSourceManager();
+ SourceLocation startLoc = SM.getExpansionLoc(compat::getBeginLoc(parent));
+ SourceLocation endLoc = SM.getExpansionLoc(compat::getEndLoc(parent));
+ const char* p1 = SM.getCharacterData(startLoc);
+ const char* p2 = SM.getCharacterData(endLoc);
+ unsigned n = Lexer::MeasureTokenLength(endLoc, SM, compiler.getLangOpts());
+ if (p2 < p1) // clang is misbehaving, appears to be macro constant related
+ return false;
+ std::string callText(p1, p2 - p1 + n);
+ auto originalLength = callText.size();
+
+ auto newText = std::regex_replace(callText, std::regex(methodName + " *\\( *\\) *="),
+ setPrefix + methodName + "(");
+ if (newText == callText)
+ return false;
+ newText += " )";
+
+ return replaceText(startLoc, originalLength, newText);
+}
+
+bool ChangeToolsGen::ChangeBinaryOperatorPlusMinus(BinaryOperator const* binaryOp,
+ CXXMemberCallExpr const* call,
+ std::string const& methodName)
+{
+ // Look for expressions like
+ // aRect.Left() += ...;
+ // and replace with
+ // aRect.MoveLeft( ... );
+ SourceManager& SM = compiler.getSourceManager();
+ SourceLocation startLoc = SM.getExpansionLoc(compat::getBeginLoc(binaryOp));
+ SourceLocation endLoc = SM.getExpansionLoc(compat::getEndLoc(binaryOp));
+ const char* p1 = SM.getCharacterData(startLoc);
+ const char* p2 = SM.getCharacterData(endLoc);
+ if (p2 < p1) // clang is misbehaving, appears to be macro constant related
+ return false;
+ unsigned n = Lexer::MeasureTokenLength(endLoc, SM, compiler.getLangOpts());
+ std::string callText(p1, p2 - p1 + n);
+ auto originalLength = callText.size();
+
+ std::string newText;
+ if (binaryOp->getOpcode() == BO_AddAssign)
+ {
+ newText = std::regex_replace(callText, std::regex(methodName + " *\\( *\\) *\\+= *"),
+ "Adjust" + methodName + "(");
+ newText += " )";
+ }
+ else
+ {
+ newText = std::regex_replace(callText, std::regex(methodName + " *\\( *\\) *\\-= *"),
+ "Adjust" + methodName + "( -(");
+ newText += ") )";
+ }
+
+ if (newText == callText)
+ {
+ report(DiagnosticsEngine::Warning, "binaryop-plusminus regex match failed",
+ compat::getBeginLoc(call));
+ return false;
+ }
+
+ return replaceText(startLoc, originalLength, newText);
+}
+
+bool ChangeToolsGen::ChangeBinaryOperatorOther(BinaryOperator const* binaryOp,
+ CXXMemberCallExpr const* call,
+ std::string const& methodName,
+ std::string const& setPrefix)
+{
+ // Look for expressions like
+ // aRect.Left() += ...;
+ // and replace with
+ // aRect.SetLeft( aRect.GetLeft() + ... );
+ SourceManager& SM = compiler.getSourceManager();
+ SourceLocation startLoc = SM.getExpansionLoc(compat::getBeginLoc(binaryOp));
+ SourceLocation endLoc = SM.getExpansionLoc(compat::getEndLoc(binaryOp));
+ const char* p1 = SM.getCharacterData(startLoc);
+ const char* p2 = SM.getCharacterData(endLoc);
+ if (p2 < p1) // clang is misbehaving, appears to be macro constant related
+ return false;
+ unsigned n = Lexer::MeasureTokenLength(endLoc, SM, compiler.getLangOpts());
+ std::string callText(p1, p2 - p1 + n);
+ auto originalLength = callText.size();
+
+ std::string regexOpname;
+ std::string replaceOpname;
+ switch (binaryOp->getOpcode())
+ {
+ case BO_RemAssign:
+ regexOpname = "\\%=";
+ replaceOpname = "%";
+ break;
+ case BO_MulAssign:
+ regexOpname = "\\*=";
+ replaceOpname = "*";
+ break;
+ case BO_DivAssign:
+ regexOpname = "\\/=";
+ replaceOpname = "/";
+ break;
+ default:
+ assert(false);
+ }
+
+ auto implicitObjectText = extractCode(call->getImplicitObjectArgument()->getExprLoc(),
+ call->getImplicitObjectArgument()->getExprLoc());
+ std::string reString(methodName + " *\\( *\\) *" + regexOpname);
+ auto newText = std::regex_replace(callText, std::regex(reString),
+ setPrefix + methodName + "( " + implicitObjectText + "."
+ + methodName + "() " + replaceOpname + " (");
+ if (newText == callText)
+ {
+ report(DiagnosticsEngine::Warning, "binaryop-other regex match failed %0",
+ compat::getBeginLoc(call))
+ << reString;
+ return false;
+ }
+ // sometimes we end up with duplicate spaces after the opname
+ newText
+ = std::regex_replace(newText, std::regex(methodName + "\\(\\) \\" + replaceOpname + " "),
+ methodName + "() " + replaceOpname + " ");
+ newText += ") )";
+
+ return replaceText(startLoc, originalLength, newText);
+}
+
+bool ChangeToolsGen::ChangeUnaryOperator(UnaryOperator const* unaryOp,
+ CXXMemberCallExpr const* call,
+ std::string const& methodName)
+{
+ // Look for expressions like
+ // aRect.Left()++;
+ // ++aRect.Left();
+ // and replace with
+ // aRect.MoveLeft( 1 );
+
+ SourceManager& SM = compiler.getSourceManager();
+ SourceLocation startLoc = SM.getExpansionLoc(compat::getBeginLoc(unaryOp));
+ SourceLocation endLoc = SM.getExpansionLoc(compat::getEndLoc(unaryOp));
+ const char* p1 = SM.getCharacterData(startLoc);
+ const char* p2 = SM.getCharacterData(endLoc);
+ if (p2 < p1) // clang is misbehaving, appears to be macro constant related
+ return false;
+ unsigned n = Lexer::MeasureTokenLength(endLoc, SM, compiler.getLangOpts());
+ std::string callText(p1, p2 - p1 + n);
+ auto originalLength = callText.size();
+
+ auto implicitObjectText = extractCode(call->getImplicitObjectArgument()->getExprLoc(),
+ call->getImplicitObjectArgument()->getExprLoc());
+ auto op = unaryOp->getOpcode();
+ std::string regexOpname;
+ std::string replaceOp;
+ switch (op)
+ {
+ case UO_PostInc:
+ case UO_PreInc:
+ replaceOp = "1";
+ regexOpname = "\\+\\+";
+ break;
+ case UO_PostDec:
+ case UO_PreDec:
+ replaceOp = "-1";
+ regexOpname = "\\-\\-";
+ break;
+ default:
+ assert(false);
+ }
+ std::string newText;
+ std::string reString;
+ if (op == UO_PostInc || op == UO_PostDec)
+ {
+ reString = methodName + " *\\( *\\) *" + regexOpname;
+ newText = std::regex_replace(callText, std::regex(reString),
+ "Adjust" + methodName + "( " + replaceOp);
+ }
+ else
+ {
+ newText = implicitObjectText + "." + "Adjust" + methodName + "( " + replaceOp;
+ }
+ if (newText == callText)
+ {
+ report(DiagnosticsEngine::Warning, "unaryop regex match failed %0",
+ compat::getBeginLoc(call))
+ << reString;
+ return false;
+ }
+ newText += " )";
+ return replaceText(startLoc, originalLength, newText);
+}
+
+std::string ChangeToolsGen::extractCode(SourceLocation startLoc, SourceLocation endLoc)
+{
+ SourceManager& SM = compiler.getSourceManager();
+ const char* p1 = SM.getCharacterData(SM.getExpansionLoc(startLoc));
+ const char* p2 = SM.getCharacterData(SM.getExpansionLoc(endLoc));
+ unsigned n = Lexer::MeasureTokenLength(endLoc, SM, compiler.getLangOpts());
+ return std::string(p1, p2 - p1 + n);
+}
+
+static loplugin::Plugin::Registration<ChangeToolsGen> X("changetoolsgen", false);
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */