bump product version to 6.4.0.3
[LibreOffice.git] / compilerplugins / clang / changetoolsgen.cxx
blobb5eda7dbb826132371cf95bd9f6f7a226cb7b308
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * Based on LLVM/Clang.
7 * This file is distributed under the University of Illinois Open Source
8 * License. See LICENSE.TXT for details.
12 #include "plugin.hxx"
13 #include "check.hxx"
14 #include <regex>
16 /**
17 * Changes calls to tools::Rectangle/Point/Size methods that return a ref to instead call the setter methods.
19 * run as:
20 * make COMPILER_PLUGIN_TOOL=changetoolsgen UPDATE_FILES=all FORCE_COMPILE_ALL=1
21 * or
22 * make <module> COMPILER_PLUGIN_TOOL=changetoolsgen FORCE_COMPILE_ALL=1
25 namespace
27 class ChangeToolsGen : public loplugin::FilteringRewritePlugin<ChangeToolsGen>
29 public:
30 explicit ChangeToolsGen(loplugin::InstantiationData const& data)
31 : FilteringRewritePlugin(data)
34 virtual void run() override;
35 bool VisitCXXMemberCallExpr(CXXMemberCallExpr const* call);
37 private:
38 bool ChangeAssignment(Stmt const* parent, std::string const& methodName,
39 std::string const& setPrefix);
40 bool ChangeBinaryOperatorPlusMinus(BinaryOperator const* parent, CXXMemberCallExpr const* call,
41 std::string const& methodName);
42 bool ChangeBinaryOperatorOther(BinaryOperator const* parent, CXXMemberCallExpr const* call,
43 std::string const& methodName, std::string const& setPrefix);
44 bool ChangeUnaryOperator(UnaryOperator const* parent, CXXMemberCallExpr const* call,
45 std::string const& methodName);
46 std::string extractCode(SourceLocation startLoc, SourceLocation endLoc);
49 void ChangeToolsGen::run() { TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); }
51 bool ChangeToolsGen::VisitCXXMemberCallExpr(CXXMemberCallExpr const* call)
53 if (ignoreLocation(call))
54 return true;
55 const CXXMethodDecl* func = call->getMethodDecl();
56 if (!func)
57 return true;
58 if (func->isConst())
59 return true;
60 auto dc = loplugin::DeclCheck(func);
61 std::string methodName;
62 std::string setPrefix;
63 if (dc.Function("Top").Class("Rectangle").Namespace("tools").GlobalNamespace())
65 methodName = "Top";
66 setPrefix = "Set";
68 else if (dc.Function("Bottom").Class("Rectangle").Namespace("tools").GlobalNamespace())
70 methodName = "Bottom";
71 setPrefix = "Set";
73 else if (dc.Function("Left").Class("Rectangle").Namespace("tools").GlobalNamespace())
75 methodName = "Left";
76 setPrefix = "Set";
78 else if (dc.Function("Right").Class("Rectangle").Namespace("tools").GlobalNamespace())
80 methodName = "Right";
81 setPrefix = "Set";
83 else if (dc.Function("X").Class("Point").GlobalNamespace())
85 methodName = "X";
86 setPrefix = "set";
88 else if (dc.Function("Y").Class("Point").GlobalNamespace())
90 methodName = "Y";
91 setPrefix = "set";
93 else if (dc.Function("Width").Class("Size").GlobalNamespace())
95 methodName = "Width";
96 setPrefix = "set";
98 else if (dc.Function("Height").Class("Size").GlobalNamespace())
100 methodName = "Height";
101 setPrefix = "set";
103 else
104 return true;
105 if (!loplugin::TypeCheck(func->getReturnType()).LvalueReference())
106 return true;
108 auto parent = getParentStmt(call);
109 if (!parent)
110 return true;
111 if (auto unaryOp = dyn_cast<UnaryOperator>(parent))
113 if (!ChangeUnaryOperator(unaryOp, call, methodName))
114 report(DiagnosticsEngine::Warning, "Could not fix, unary", compat::getBeginLoc(call));
115 return true;
117 auto binaryOp = dyn_cast<BinaryOperator>(parent);
118 if (!binaryOp)
120 // normal getter
121 return true;
123 auto opcode = binaryOp->getOpcode();
124 if (opcode == BO_Assign)
126 // Check for assignments embedded inside other expressions
127 auto parent2 = getParentStmt(parent);
128 if (dyn_cast_or_null<ExprWithCleanups>(parent2))
129 parent2 = getParentStmt(parent2);
130 if (parent2 && isa<Expr>(parent2))
132 report(DiagnosticsEngine::Warning, "Could not fix, embedded assign",
133 compat::getBeginLoc(call));
134 return true;
136 // Check for
137 // X.Width() = X.Height() = 1;
138 if (auto rhs = dyn_cast<BinaryOperator>(binaryOp->getRHS()->IgnoreParenImpCasts()))
139 if (rhs->getOpcode() == BO_Assign)
141 report(DiagnosticsEngine::Warning, "Could not fix, double assign",
142 compat::getBeginLoc(call));
143 return true;
145 if (!ChangeAssignment(parent, methodName, setPrefix))
146 report(DiagnosticsEngine::Warning, "Could not fix, assign", compat::getBeginLoc(call));
147 return true;
149 if (opcode == BO_AddAssign || opcode == BO_SubAssign)
151 if (!ChangeBinaryOperatorPlusMinus(binaryOp, call, methodName))
152 report(DiagnosticsEngine::Warning, "Could not fix, assign-and-change",
153 compat::getBeginLoc(call));
154 return true;
156 else if (opcode == BO_RemAssign || opcode == BO_MulAssign || opcode == BO_DivAssign)
158 if (!ChangeBinaryOperatorOther(binaryOp, call, methodName, setPrefix))
159 report(DiagnosticsEngine::Warning, "Could not fix, assign-and-change",
160 compat::getBeginLoc(call));
161 return true;
163 else
164 assert(false);
165 return true;
168 bool ChangeToolsGen::ChangeAssignment(Stmt const* parent, std::string const& methodName,
169 std::string const& setPrefix)
171 // Look for expressions like
172 // aRect.Left() = ...;
173 // and replace with
174 // aRect.SetLeft( ... );
175 SourceManager& SM = compiler.getSourceManager();
176 SourceLocation startLoc = SM.getExpansionLoc(compat::getBeginLoc(parent));
177 SourceLocation endLoc = SM.getExpansionLoc(compat::getEndLoc(parent));
178 const char* p1 = SM.getCharacterData(startLoc);
179 const char* p2 = SM.getCharacterData(endLoc);
180 unsigned n = Lexer::MeasureTokenLength(endLoc, SM, compiler.getLangOpts());
181 if (p2 < p1) // clang is misbehaving, appears to be macro constant related
182 return false;
183 std::string callText(p1, p2 - p1 + n);
184 auto originalLength = callText.size();
186 auto newText = std::regex_replace(callText, std::regex(methodName + " *\\( *\\) *="),
187 setPrefix + methodName + "(");
188 if (newText == callText)
189 return false;
190 newText += " )";
192 return replaceText(startLoc, originalLength, newText);
195 bool ChangeToolsGen::ChangeBinaryOperatorPlusMinus(BinaryOperator const* binaryOp,
196 CXXMemberCallExpr const* call,
197 std::string const& methodName)
199 // Look for expressions like
200 // aRect.Left() += ...;
201 // and replace with
202 // aRect.MoveLeft( ... );
203 SourceManager& SM = compiler.getSourceManager();
204 SourceLocation startLoc = SM.getExpansionLoc(compat::getBeginLoc(binaryOp));
205 SourceLocation endLoc = SM.getExpansionLoc(compat::getEndLoc(binaryOp));
206 const char* p1 = SM.getCharacterData(startLoc);
207 const char* p2 = SM.getCharacterData(endLoc);
208 if (p2 < p1) // clang is misbehaving, appears to be macro constant related
209 return false;
210 unsigned n = Lexer::MeasureTokenLength(endLoc, SM, compiler.getLangOpts());
211 std::string callText(p1, p2 - p1 + n);
212 auto originalLength = callText.size();
214 std::string newText;
215 if (binaryOp->getOpcode() == BO_AddAssign)
217 newText = std::regex_replace(callText, std::regex(methodName + " *\\( *\\) *\\+= *"),
218 "Adjust" + methodName + "(");
219 newText += " )";
221 else
223 newText = std::regex_replace(callText, std::regex(methodName + " *\\( *\\) *\\-= *"),
224 "Adjust" + methodName + "( -(");
225 newText += ") )";
228 if (newText == callText)
230 report(DiagnosticsEngine::Warning, "binaryop-plusminus regex match failed",
231 compat::getBeginLoc(call));
232 return false;
235 return replaceText(startLoc, originalLength, newText);
238 bool ChangeToolsGen::ChangeBinaryOperatorOther(BinaryOperator const* binaryOp,
239 CXXMemberCallExpr const* call,
240 std::string const& methodName,
241 std::string const& setPrefix)
243 // Look for expressions like
244 // aRect.Left() += ...;
245 // and replace with
246 // aRect.SetLeft( aRect.GetLeft() + ... );
247 SourceManager& SM = compiler.getSourceManager();
248 SourceLocation startLoc = SM.getExpansionLoc(compat::getBeginLoc(binaryOp));
249 SourceLocation endLoc = SM.getExpansionLoc(compat::getEndLoc(binaryOp));
250 const char* p1 = SM.getCharacterData(startLoc);
251 const char* p2 = SM.getCharacterData(endLoc);
252 if (p2 < p1) // clang is misbehaving, appears to be macro constant related
253 return false;
254 unsigned n = Lexer::MeasureTokenLength(endLoc, SM, compiler.getLangOpts());
255 std::string callText(p1, p2 - p1 + n);
256 auto originalLength = callText.size();
258 std::string regexOpname;
259 std::string replaceOpname;
260 switch (binaryOp->getOpcode())
262 case BO_RemAssign:
263 regexOpname = "\\%=";
264 replaceOpname = "%";
265 break;
266 case BO_MulAssign:
267 regexOpname = "\\*=";
268 replaceOpname = "*";
269 break;
270 case BO_DivAssign:
271 regexOpname = "\\/=";
272 replaceOpname = "/";
273 break;
274 default:
275 assert(false);
278 auto implicitObjectText = extractCode(call->getImplicitObjectArgument()->getExprLoc(),
279 call->getImplicitObjectArgument()->getExprLoc());
280 std::string reString(methodName + " *\\( *\\) *" + regexOpname);
281 auto newText = std::regex_replace(callText, std::regex(reString),
282 setPrefix + methodName + "( " + implicitObjectText + "."
283 + methodName + "() " + replaceOpname + " (");
284 if (newText == callText)
286 report(DiagnosticsEngine::Warning, "binaryop-other regex match failed %0",
287 compat::getBeginLoc(call))
288 << reString;
289 return false;
291 // sometimes we end up with duplicate spaces after the opname
292 newText
293 = std::regex_replace(newText, std::regex(methodName + "\\(\\) \\" + replaceOpname + " "),
294 methodName + "() " + replaceOpname + " ");
295 newText += ") )";
297 return replaceText(startLoc, originalLength, newText);
300 bool ChangeToolsGen::ChangeUnaryOperator(UnaryOperator const* unaryOp,
301 CXXMemberCallExpr const* call,
302 std::string const& methodName)
304 // Look for expressions like
305 // aRect.Left()++;
306 // ++aRect.Left();
307 // and replace with
308 // aRect.MoveLeft( 1 );
310 SourceManager& SM = compiler.getSourceManager();
311 SourceLocation startLoc = SM.getExpansionLoc(compat::getBeginLoc(unaryOp));
312 SourceLocation endLoc = SM.getExpansionLoc(compat::getEndLoc(unaryOp));
313 const char* p1 = SM.getCharacterData(startLoc);
314 const char* p2 = SM.getCharacterData(endLoc);
315 if (p2 < p1) // clang is misbehaving, appears to be macro constant related
316 return false;
317 unsigned n = Lexer::MeasureTokenLength(endLoc, SM, compiler.getLangOpts());
318 std::string callText(p1, p2 - p1 + n);
319 auto originalLength = callText.size();
321 auto implicitObjectText = extractCode(call->getImplicitObjectArgument()->getExprLoc(),
322 call->getImplicitObjectArgument()->getExprLoc());
323 auto op = unaryOp->getOpcode();
324 std::string regexOpname;
325 std::string replaceOp;
326 switch (op)
328 case UO_PostInc:
329 case UO_PreInc:
330 replaceOp = "1";
331 regexOpname = "\\+\\+";
332 break;
333 case UO_PostDec:
334 case UO_PreDec:
335 replaceOp = "-1";
336 regexOpname = "\\-\\-";
337 break;
338 default:
339 assert(false);
341 std::string newText;
342 std::string reString;
343 if (op == UO_PostInc || op == UO_PostDec)
345 reString = methodName + " *\\( *\\) *" + regexOpname;
346 newText = std::regex_replace(callText, std::regex(reString),
347 "Adjust" + methodName + "( " + replaceOp);
349 else
351 newText = implicitObjectText + "." + "Adjust" + methodName + "( " + replaceOp;
353 if (newText == callText)
355 report(DiagnosticsEngine::Warning, "unaryop regex match failed %0",
356 compat::getBeginLoc(call))
357 << reString;
358 return false;
360 newText += " )";
361 return replaceText(startLoc, originalLength, newText);
364 std::string ChangeToolsGen::extractCode(SourceLocation startLoc, SourceLocation endLoc)
366 SourceManager& SM = compiler.getSourceManager();
367 const char* p1 = SM.getCharacterData(SM.getExpansionLoc(startLoc));
368 const char* p2 = SM.getCharacterData(SM.getExpansionLoc(endLoc));
369 unsigned n = Lexer::MeasureTokenLength(endLoc, SM, compiler.getLangOpts());
370 return std::string(p1, p2 - p1 + n);
373 static loplugin::Plugin::Registration<ChangeToolsGen> X("changetoolsgen", false);
375 } // namespace
377 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */