bump product version to 6.4.0.3
[LibreOffice.git] / compilerplugins / clang / conditionalstring.cxx
blobd09472c25446f9f1fda6d66fe5744f4f56c2cb3c
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #ifndef LO_CLANG_SHARED_PLUGINS
12 #include <cassert>
14 #include "check.hxx"
15 #include "compat.hxx"
16 #include "plugin.hxx"
18 // Find uses of OUString in conditional expressions that could be rewritten as OUStringLiteral, as
19 // in
21 // s += (b ? OUString("xy") : OUString(z");
23 namespace
25 // Like Expr::IgnoreImplicit, but for an ImplicitCastExpr skips to getSubExprAsWritten (so skips a
26 // CXXConstructExpr where Expr::IgnoreImplicit would stop):
27 Expr const* ignoreImplicit(Expr const* expr)
29 for (auto e = expr;;)
31 if (auto const e1 = dyn_cast<ImplicitCastExpr>(e))
33 e = e1->getSubExprAsWritten();
35 #if CLANG_VERSION >= 80000
36 else if (auto const e2 = dyn_cast<FullExpr>(e))
38 e = e2->getSubExpr();
40 #endif
41 else if (auto const e3 = dyn_cast<MaterializeTemporaryExpr>(e))
43 e = e3->GetTemporaryExpr();
45 else if (auto const e4 = dyn_cast<CXXBindTemporaryExpr>(e))
47 e = e4->getSubExpr();
49 else
51 return e;
56 class ConditionalString final : public loplugin::FilteringPlugin<ConditionalString>
58 public:
59 explicit ConditionalString(loplugin::InstantiationData const& data)
60 : FilteringPlugin(data)
64 bool VisitCallExpr(CallExpr const* expr)
66 if (ignoreLocation(expr))
68 return true;
70 auto const fn = expr->getDirectCallee();
71 if (fn == nullptr)
73 return true;
75 //TODO: Instead of a hardcoded list of functions, check that `fn` has overloads taking
76 // OUString and OUStringLiteral, respectively (and operator + is even more complicated than
77 // that, going via ToStringHelper<OUStringLiteral> specialization; the getNumArgs checks for
78 // the various functions are meant to guard against the unlikely case that the affected
79 // parameters get defaulted in the future; overloaded operators cannot generally have
80 // defaulted parameters):
81 loplugin::DeclCheck const dc(fn);
82 if (dc.Operator(OO_Equal).Class("OUString").Namespace("rtl").GlobalNamespace())
84 assert(fn->getNumParams() == 1);
85 if (isa<CXXOperatorCallExpr>(expr))
87 assert(expr->getNumArgs() == 2);
88 check(expr->getArg(1));
90 else
92 assert(expr->getNumArgs() == 1);
93 check(expr->getArg(0));
95 return true;
97 if (dc.Operator(OO_PlusEqual).Class("OUString").Namespace("rtl").GlobalNamespace())
99 assert(fn->getNumParams() == 1);
100 if (isa<CXXOperatorCallExpr>(expr))
102 assert(expr->getNumArgs() == 2);
103 check(expr->getArg(1));
105 else
107 assert(expr->getNumArgs() == 1);
108 check(expr->getArg(0));
110 return true;
112 if (dc.Function("reverseCompareTo").Class("OUString").Namespace("rtl").GlobalNamespace()
113 && fn->getNumParams() == 1)
115 if (expr->getNumArgs() >= 1)
117 check(expr->getArg(0));
119 return true;
121 if (dc.Function("equalsIgnoreAsciiCase")
122 .Class("OUString")
123 .Namespace("rtl")
124 .GlobalNamespace()
125 && fn->getNumParams() == 1)
127 if (expr->getNumArgs() >= 1)
129 check(expr->getArg(0));
131 return true;
133 if (dc.Function("match").Class("OUString").Namespace("rtl").GlobalNamespace()
134 && fn->getNumParams() == 2)
136 if (expr->getNumArgs() >= 1)
138 check(expr->getArg(0));
140 return true;
142 if (dc.Function("matchIgnoreAsciiCase").Class("OUString").Namespace("rtl").GlobalNamespace()
143 && fn->getNumParams() == 2)
145 if (expr->getNumArgs() >= 1)
147 check(expr->getArg(0));
149 return true;
151 if (dc.Function("startsWith").Class("OUString").Namespace("rtl").GlobalNamespace()
152 && fn->getNumParams() == 2)
154 if (expr->getNumArgs() >= 1)
156 check(expr->getArg(0));
158 return true;
160 if (dc.Function("startsWithIgnoreAsciiCase")
161 .Class("OUString")
162 .Namespace("rtl")
163 .GlobalNamespace()
164 && fn->getNumParams() == 2)
166 if (expr->getNumArgs() >= 1)
168 check(expr->getArg(0));
170 return true;
172 if (dc.Function("endsWith").Class("OUString").Namespace("rtl").GlobalNamespace()
173 && fn->getNumParams() == 2)
175 if (expr->getNumArgs() >= 1)
177 check(expr->getArg(0));
179 return true;
181 if (dc.Function("endsWithIgnoreAsciiCase")
182 .Class("OUString")
183 .Namespace("rtl")
184 .GlobalNamespace()
185 && fn->getNumParams() == 2)
187 if (expr->getNumArgs() >= 1)
189 check(expr->getArg(0));
191 return true;
193 if (dc.Operator(OO_EqualEqual)
194 .Namespace("rtl")
195 .GlobalNamespace()) //TODO: more precicse check
197 assert(fn->getNumParams() == 2);
198 assert(expr->getNumArgs() == 2);
199 check(expr->getArg(0));
200 check(expr->getArg(1));
201 return true;
203 if (dc.Operator(OO_ExclaimEqual)
204 .Namespace("rtl")
205 .GlobalNamespace()) //TODO: more precicse check
207 assert(fn->getNumParams() == 2);
208 assert(expr->getNumArgs() == 2);
209 check(expr->getArg(0));
210 check(expr->getArg(1));
211 return true;
213 if (dc.Operator(OO_Less).Namespace("rtl").GlobalNamespace()) //TODO: more precicse check
215 assert(fn->getNumParams() == 2);
216 assert(expr->getNumArgs() == 2);
217 check(expr->getArg(0));
218 check(expr->getArg(1));
219 return true;
221 if (dc.Operator(OO_LessEqual)
222 .Namespace("rtl")
223 .GlobalNamespace()) //TODO: more precicse check
225 assert(fn->getNumParams() == 2);
226 assert(expr->getNumArgs() == 2);
227 check(expr->getArg(0));
228 check(expr->getArg(1));
229 return true;
231 if (dc.Operator(OO_Greater).Namespace("rtl").GlobalNamespace()) //TODO: more precicse check
233 assert(fn->getNumParams() == 2);
234 assert(expr->getNumArgs() == 2);
235 check(expr->getArg(0));
236 check(expr->getArg(1));
237 return true;
239 if (dc.Operator(OO_GreaterEqual)
240 .Namespace("rtl")
241 .GlobalNamespace()) //TODO: more precicse check
243 assert(fn->getNumParams() == 2);
244 assert(expr->getNumArgs() == 2);
245 check(expr->getArg(0));
246 check(expr->getArg(1));
247 return true;
249 if (dc.Function("indexOf").Class("OUString").Namespace("rtl").GlobalNamespace()
250 && fn->getNumParams() == 2)
252 if (expr->getNumArgs() >= 1)
254 check(expr->getArg(0));
256 return true;
258 if (dc.Function("lastIndexOf").Class("OUString").Namespace("rtl").GlobalNamespace()
259 && fn->getNumParams() == 1)
261 if (expr->getNumArgs() >= 1)
263 check(expr->getArg(0));
265 return true;
267 if (dc.Function("replaceFirst").Class("OUString").Namespace("rtl").GlobalNamespace()
268 && fn->getNumParams() == 3)
270 if (expr->getNumArgs() >= 1)
272 check(expr->getArg(0));
274 if (expr->getNumArgs() >= 2)
276 check(expr->getArg(1));
278 return true;
280 if (dc.Function("replaceAll").Class("OUString").Namespace("rtl").GlobalNamespace()
281 && fn->getNumParams() == 2)
283 if (expr->getNumArgs() >= 1)
285 check(expr->getArg(0));
287 if (expr->getNumArgs() >= 2)
289 check(expr->getArg(1));
291 return true;
293 if (dc.Operator(OO_Plus).Namespace("rtl").GlobalNamespace()
294 && fn->getNumParams() == 2) //TODO: more precicse check
296 assert(expr->getNumArgs() == 2);
297 check(expr->getArg(0));
298 check(expr->getArg(1));
299 return true;
301 if (dc.Operator(OO_Equal).Class("OUStringBuffer").Namespace("rtl").GlobalNamespace())
303 assert(fn->getNumParams() == 1);
304 if (isa<CXXOperatorCallExpr>(expr))
306 assert(expr->getNumArgs() == 2);
307 check(expr->getArg(1));
309 else
311 assert(expr->getNumArgs() == 1);
312 check(expr->getArg(0));
314 return true;
316 if (dc.Function("append").Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
317 && fn->getNumParams() == 1)
319 if (expr->getNumArgs() >= 1)
321 check(expr->getArg(0));
323 return true;
325 if (dc.Function("insert").Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
326 && fn->getNumParams() == 2)
328 if (expr->getNumArgs() >= 2)
330 check(expr->getArg(1));
332 return true;
334 if (dc.Function("indexOf").Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
335 && fn->getNumParams() == 2)
337 if (expr->getNumArgs() >= 1)
339 check(expr->getArg(0));
341 return true;
343 if (dc.Function("lastIndexOf").Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
344 && fn->getNumParams() == 1)
346 if (expr->getNumArgs() >= 1)
348 check(expr->getArg(0));
350 return true;
352 return true;
355 bool preRun() override { return compiler.getLangOpts().CPlusPlus; }
357 private:
358 enum class Kind
360 OUStringFromLiteral,
361 OUStringLiteralOrVoid,
362 Other
365 void run() override
367 if (preRun())
369 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
373 Kind getKind(Expr const* expr)
375 auto const tc = loplugin::TypeCheck(ignoreImplicit(expr)->getType());
376 if (tc.Struct("OUStringLiteral").Namespace("rtl").GlobalNamespace() || tc.Void())
378 return Kind::OUStringLiteralOrVoid;
380 if (loplugin::TypeCheck(expr->getType())
381 .Class("OUString")
382 .Namespace("rtl")
383 .GlobalNamespace())
385 // Check for both explicit
387 // OUString("...")
389 // and implicit
391 // "..."
393 // expressions:
394 auto e = expr->IgnoreParens();
395 if (auto const e1 = dyn_cast<CXXFunctionalCastExpr>(e))
397 e = e1->getSubExpr();
399 if (auto const e1
400 = dyn_cast<CXXConstructExpr>(compat::IgnoreImplicit(e)->IgnoreParens()))
402 if (e1->getNumArgs() != 0 //TODO
403 && isa<clang::StringLiteral>(e1->getArg(0)->IgnoreParenImpCasts()))
405 return Kind::OUStringFromLiteral;
409 return Kind::Other;
412 void check(Expr const* expr)
414 //TODO: skip `,`; handle ?: chains
415 auto const cond = dyn_cast<ConditionalOperator>(expr->IgnoreParenImpCasts());
416 if (cond == nullptr)
418 return;
420 auto const k1 = getKind(cond->getTrueExpr());
421 if (k1 == Kind::Other)
423 return;
425 auto const k2 = getKind(cond->getFalseExpr());
426 if (k2 == Kind::Other
427 || (k1 == Kind::OUStringLiteralOrVoid && k2 == Kind::OUStringLiteralOrVoid))
429 return;
431 if (k1 == Kind::OUStringFromLiteral && k2 == Kind::OUStringFromLiteral)
433 report(DiagnosticsEngine::Warning,
434 ("replace both 2nd and 3rd operands of conditional expression with"
435 " `rtl::OUStringLiteral`"),
436 cond->getExprLoc())
437 << cond->getSourceRange();
439 else
441 assert((k1 == Kind::OUStringFromLiteral && k2 == Kind::OUStringLiteralOrVoid)
442 || (k1 == Kind::OUStringLiteralOrVoid && k2 == Kind::OUStringFromLiteral));
443 auto const second = k1 == Kind::OUStringFromLiteral;
444 auto const sub
445 = (second ? cond->getTrueExpr() : cond->getFalseExpr())->IgnoreParenImpCasts();
446 report(DiagnosticsEngine::Warning,
447 ("replace %select{2nd|3rd}0 operand of conditional expression with"
448 " `rtl::OUStringLiteral`"),
449 sub->getExprLoc())
450 << (second ? 0 : 1) << sub->getSourceRange();
451 report(DiagnosticsEngine::Note, "conditional expression is here", cond->getExprLoc())
452 << cond->getSourceRange();
457 loplugin::Plugin::Registration<ConditionalString> conditionalstring("conditionalstring");
460 #endif
462 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */