cid#1555694 COPY_INSTEAD_OF_MOVE
[LibreOffice.git] / compilerplugins / clang / ostr.cxx
blob189b74da315212dcd59f263660c357eb6097f8ab
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 #include <cassert>
11 #include <set>
12 #include <stack>
14 #include "check.hxx"
15 #include "compat.hxx"
16 #include "plugin.hxx"
18 // Rewrite some uses of O[U]String to use ""_ostr/u""_ustr literals.
20 namespace
22 class Ostr : public loplugin::FilteringRewritePlugin<Ostr>
24 public:
25 explicit Ostr(loplugin::InstantiationData const& data)
26 : FilteringRewritePlugin(data)
30 // Needed so that e.g.
32 // struct S { OUString s; };
33 // S s = {u"foo"};
35 // is caught:
36 bool shouldVisitImplicitCode() const { return true; }
38 void run() override
40 if (compiler.getLangOpts().CPlusPlus
41 && TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()))
43 for (auto const& i : vars_)
45 auto const utf16
46 = bool(loplugin::TypeCheck(i.first->getType()).Class("OUStringLiteral"));
47 if (i.second.singleUse == nullptr)
49 if (rewriter != nullptr)
51 auto e = i.first->getInit()->IgnoreParenImpCasts();
52 if (auto const e2 = dyn_cast<ConstantExpr>(e))
54 e = e2->getSubExpr()->IgnoreParenImpCasts();
56 if (auto const e2 = dyn_cast<CXXConstructExpr>(e))
58 assert(e2->getNumArgs() == 1);
59 e = e2->getArg(0)->IgnoreParenImpCasts();
61 e = dyn_cast<clang::StringLiteral>(e);
62 // e is null when this OUStringLiteral is initialized with another
63 // OUStringLiteral:
64 if (e == nullptr
65 || insertTextAfterToken(e->getEndLoc(), utf16 ? "_ustr" : "_ostr"))
67 auto ok = true;
68 for (auto d = i.first->getMostRecentDecl(); d != nullptr;
69 d = d->getPreviousDecl())
71 auto const l1 = d->getTypeSpecStartLoc();
72 auto l2 = d->getTypeSpecEndLoc();
73 l2 = l2.getLocWithOffset(Lexer::MeasureTokenLength(
74 l2, compiler.getSourceManager(), compiler.getLangOpts()));
75 if (!replaceText(l1, delta(l1, l2), utf16 ? "OUString" : "OString"))
77 ok = false;
80 for (auto const i : i.second.explicitConversions)
82 auto const e2 = i->getArg(0);
83 auto l1 = i->getBeginLoc();
84 auto l2 = e2->getBeginLoc();
85 auto l3 = e2->getEndLoc();
86 auto l4 = i->getEndLoc();
87 while (compiler.getSourceManager().isMacroArgExpansion(l1)
88 && compiler.getSourceManager().isMacroArgExpansion(l2)
89 && compiler.getSourceManager().isMacroArgExpansion(l3)
90 && compiler.getSourceManager().isMacroArgExpansion(l4))
91 //TODO: check all four locations are part of the same macro argument
92 // expansion
94 l1 = compiler.getSourceManager().getImmediateMacroCallerLoc(l1);
95 l2 = compiler.getSourceManager().getImmediateMacroCallerLoc(l2);
96 l3 = compiler.getSourceManager().getImmediateMacroCallerLoc(l3);
97 l4 = compiler.getSourceManager().getImmediateMacroCallerLoc(l4);
99 l3 = l3.getLocWithOffset(Lexer::MeasureTokenLength(
100 l3, compiler.getSourceManager(), compiler.getLangOpts()));
101 l4 = l4.getLocWithOffset(Lexer::MeasureTokenLength(
102 l4, compiler.getSourceManager(), compiler.getLangOpts()));
103 removeText(l1, delta(l1, l2));
104 removeText(l3, delta(l3, l4));
106 if (ok)
108 continue;
112 report(DiagnosticsEngine::Warning,
113 "use '%select{OString|OUString}0', created from a %select{_ostr|_ustr}0 "
114 "user-defined string literal, instead of "
115 "'%select{OStringLiteral|OUStringLiteral}0' for the variable %1",
116 i.first->getLocation())
117 << utf16 << i.first->getName() << i.first->getSourceRange();
118 for (auto d = i.first->getMostRecentDecl(); d != nullptr;
119 d = d->getPreviousDecl())
121 if (d != i.first)
123 report(DiagnosticsEngine::Note, "variable %0 declared here",
124 d->getLocation())
125 << d->getName() << d->getSourceRange();
129 else
131 if (!compiler.getDiagnosticOpts().VerifyDiagnostics)
133 //TODO, left for later:
134 continue;
136 report(DiagnosticsEngine::Warning,
137 "directly use a %select{_ostr|_ustr}0 user-defined string literal "
138 "instead of introducing the intermediary "
139 "'%select{OStringLiteral|OUStringLiteral}0' variable %1",
140 i.second.singleUse->getExprLoc())
141 << utf16 << i.first->getName() << i.second.singleUse->getSourceRange();
142 for (auto d = i.first->getMostRecentDecl(); d != nullptr;
143 d = d->getPreviousDecl())
145 report(DiagnosticsEngine::Note, "intermediary variable %0 declared here",
146 d->getLocation())
147 << d->getName() << d->getSourceRange();
154 bool TraverseParmVarDecl(ParmVarDecl* decl)
156 // Otherwise,
158 // struct S { void f(int = 0); };
159 // void S::f(int) {}
161 // would visit the default argument twice:
162 if (decl->hasDefaultArg() && !decl->hasUninstantiatedDefaultArg()
163 && !decl->hasUnparsedDefaultArg() && !defaultArgs_.insert(decl->getDefaultArg()).second)
165 return true;
167 return RecursiveASTVisitor::TraverseParmVarDecl(decl);
170 bool TraverseCXXFunctionalCastExpr(CXXFunctionalCastExpr* expr)
172 functionalCasts_.push(expr);
173 auto const ret = RecursiveASTVisitor::TraverseCXXFunctionalCastExpr(expr);
174 functionalCasts_.pop();
175 return ret;
178 bool VisitVarDecl(VarDecl const* decl)
180 if (ignoreLocation(decl))
182 return true;
184 if (!decl->isThisDeclarationADefinition())
186 return true;
188 loplugin::TypeCheck const tc(decl->getType());
189 if (!(tc.Class("OStringLiteral").Namespace("rtl").GlobalNamespace()
190 || tc.Class("OUStringLiteral").Namespace("rtl").GlobalNamespace()))
192 return true;
194 if (suppressWarningAt(decl->getLocation()))
196 return true;
198 vars_[decl].multipleUses
199 = decl->getDeclContext()->isFileContext()
200 ? !compiler.getSourceManager().isInMainFile(decl->getLocation())
201 : decl->isExternallyVisible();
202 return true;
205 bool VisitDeclRefExpr(DeclRefExpr const* expr)
207 if (ignoreLocation(expr))
209 return true;
211 auto const d1 = dyn_cast<VarDecl>(expr->getDecl());
212 if (d1 == nullptr)
214 return true;
216 auto const d2 = d1->getDefinition();
217 if (d2 == nullptr)
219 return true;
221 auto const i = vars_.find(d2);
222 if (i == vars_.end())
224 return true;
226 if (!i->second.multipleUses)
228 if (i->second.singleUse == nullptr)
230 i->second.singleUse = expr;
232 else
234 i->second.multipleUses = true;
235 i->second.singleUse = nullptr;
238 return true;
241 bool VisitCXXConstructExpr(CXXConstructExpr const* expr)
243 if (ignoreLocation(expr))
245 return true;
247 auto const dc = expr->getConstructor()->getParent();
248 auto const utf16
249 = bool(loplugin::DeclCheck(dc).Class("OUString").Namespace("rtl").GlobalNamespace());
250 if (!(utf16 || loplugin::DeclCheck(dc).Class("OString").Namespace("rtl").GlobalNamespace()))
252 return true;
254 if (expr->getNumArgs() == 1
255 && loplugin::TypeCheck(expr->getArg(0)->getType())
256 .Class(utf16 ? "OUStringLiteral" : "OStringLiteral")
257 .Namespace("rtl")
258 .GlobalNamespace())
260 if (functionalCasts_.empty()
261 || functionalCasts_.top()->getSubExpr()->IgnoreImplicit() != expr)
263 return true;
265 auto const e = dyn_cast<DeclRefExpr>(expr->getArg(0)->IgnoreParenImpCasts());
266 if (e == nullptr)
268 return true;
270 auto const d1 = dyn_cast<VarDecl>(e->getDecl());
271 if (d1 == nullptr)
273 return true;
275 auto const d2 = d1->getDefinition();
276 if (d2 == nullptr)
278 return true;
280 auto const i = vars_.find(d2);
281 if (i == vars_.end())
283 return true;
285 i->second.explicitConversions.insert(expr);
286 return true;
288 if (expr->getNumArgs() != 2)
290 return true;
292 if (!loplugin::TypeCheck(expr->getArg(1)->getType())
293 .Struct("Dummy")
294 .Namespace("libreoffice_internal")
295 .Namespace("rtl")
296 .GlobalNamespace())
298 return true;
300 auto const e2 = dyn_cast<clang::StringLiteral>(expr->getArg(0)->IgnoreParenImpCasts());
301 if (e2 == nullptr)
303 return true;
305 if (!(compat::isOrdinary(e2) || e2->isUTF8()))
307 return true;
309 auto const temp = isa<CXXTemporaryObjectExpr>(expr)
310 || (!functionalCasts_.empty()
311 && functionalCasts_.top()->getSubExpr()->IgnoreImplicit() == expr);
312 auto const e1 = temp ? static_cast<Expr const*>(expr) : static_cast<Expr const*>(e2);
313 auto l1 = e1->getBeginLoc();
314 auto l2 = e2->getBeginLoc();
315 auto l3 = e2->getEndLoc();
316 auto l4 = e1->getEndLoc();
317 while (compiler.getSourceManager().isMacroArgExpansion(l1)
318 && compiler.getSourceManager().isMacroArgExpansion(l2)
319 && compiler.getSourceManager().isMacroArgExpansion(l3)
320 && compiler.getSourceManager().isMacroArgExpansion(l4))
321 //TODO: check all four locations are part of the same macro argument expansion
323 l1 = compiler.getSourceManager().getImmediateMacroCallerLoc(l1);
324 l2 = compiler.getSourceManager().getImmediateMacroCallerLoc(l2);
325 l3 = compiler.getSourceManager().getImmediateMacroCallerLoc(l3);
326 l4 = compiler.getSourceManager().getImmediateMacroCallerLoc(l4);
328 if (!locs_.insert(l1).second)
330 return true;
332 auto const macroBegin = l2.isMacroID()
333 && Lexer::isAtStartOfMacroExpansion(l2, compiler.getSourceManager(),
334 compiler.getLangOpts());
335 if (macroBegin)
337 l2 = compiler.getSourceManager().getImmediateMacroCallerLoc(l2);
339 auto const macroEnd = l3.isMacroID()
340 && Lexer::isAtEndOfMacroExpansion(l3, compiler.getSourceManager(),
341 compiler.getLangOpts());
342 if (macroEnd)
344 l3 = compiler.getSourceManager().getImmediateMacroCallerLoc(l3);
346 if (!temp)
348 l1 = l2;
349 l4 = l3;
351 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(l1)))
353 return true;
355 if (!compiler.getDiagnosticOpts().VerifyDiagnostics && utf16)
357 //TODO: Leave rewriting these uses of ordinary string literals for later (but already
358 // cover them when verifying CompilerTest_compilerplugins_clang):
359 return true;
361 if (rewriter != nullptr && isSpellingRange(l1, l2) && isSpellingRange(l3, l4))
363 l3 = l3.getLocWithOffset(
364 Lexer::MeasureTokenLength(l3, compiler.getSourceManager(), compiler.getLangOpts()));
365 l4 = l4.getLocWithOffset(
366 Lexer::MeasureTokenLength(l4, compiler.getSourceManager(), compiler.getLangOpts()));
367 if (replaceText(l1, delta(l1, l2), utf16 ? (macroBegin ? "u\"\" " : "u") : "")
368 && replaceText(l3, delta(l3, l4),
369 utf16 ? (macroEnd ? " \"\"_ustr" : "_ustr")
370 : (macroEnd ? " \"\"_ostr" : "_ostr")))
372 return true;
375 report(DiagnosticsEngine::Warning,
376 "use a %select{_ostr|_ustr}0 user-defined string literal instead of constructing an"
377 " instance of %1 from an ordinary string literal",
378 expr->getExprLoc())
379 << utf16 << expr->getType().getLocalUnqualifiedType() << expr->getSourceRange();
380 return true;
383 bool VisitCXXOperatorCallExpr(CXXOperatorCallExpr const* expr)
385 if (ignoreLocation(expr))
387 return true;
389 if (expr->getOperator() != OO_Equal)
391 return true;
393 if (!loplugin::TypeCheck(expr->getArg(0)->getType())
394 .Class("OString")
395 .Namespace("rtl")
396 .GlobalNamespace())
398 return true;
400 auto const e2 = dyn_cast<clang::StringLiteral>(expr->getArg(1)->IgnoreParenImpCasts());
401 if (e2 == nullptr)
403 return true;
405 if (rewriter != nullptr)
407 auto loc = e2->getEndLoc();
408 auto const macroEnd = loc.isMacroID()
409 && Lexer::isAtEndOfMacroExpansion(
410 loc, compiler.getSourceManager(), compiler.getLangOpts());
411 if (macroEnd)
413 loc = compiler.getSourceManager().getImmediateMacroCallerLoc(loc);
415 if (insertTextAfterToken(loc, macroEnd ? " \"\"_ostr" : "_ostr"))
417 return true;
420 report(DiagnosticsEngine::Warning,
421 "use a _ostr user-defined string literal instead of assigning from an ordinary"
422 " string literal",
423 expr->getExprLoc())
424 << expr->getSourceRange();
425 return true;
428 bool VisitCXXMemberCallExpr(CXXMemberCallExpr const* expr)
430 if (ignoreLocation(expr))
432 return true;
434 if (!loplugin::DeclCheck(expr->getMethodDecl()).Operator(OO_Equal))
436 return true;
438 if (!loplugin::TypeCheck(expr->getObjectType())
439 .Class("OString")
440 .Namespace("rtl")
441 .GlobalNamespace())
443 return true;
445 auto const e2 = dyn_cast<clang::StringLiteral>(expr->getArg(0)->IgnoreParenImpCasts());
446 if (e2 == nullptr)
448 return true;
450 if (rewriter != nullptr)
452 //TODO
454 report(DiagnosticsEngine::Warning,
455 "use a _ostr user-defined string literal instead of assigning from an ordinary"
456 " string literal",
457 expr->getExprLoc())
458 << expr->getSourceRange();
459 return true;
462 bool VisitCastExpr(CastExpr const* expr)
464 if (ignoreLocation(expr))
466 return true;
468 auto const t1 = expr->getType().getNonReferenceType();
469 auto const tc1 = loplugin::TypeCheck(t1);
470 if (!(tc1.ClassOrStruct("basic_string").StdNamespace()
471 || tc1.ClassOrStruct("basic_string_view").StdNamespace()))
473 return true;
475 auto const e2 = dyn_cast<UserDefinedLiteral>(expr->getSubExprAsWritten());
476 if (e2 == nullptr)
478 return true;
480 auto const tc2 = loplugin::TypeCheck(e2->getType());
481 if (!(tc2.Class("OString").Namespace("rtl").GlobalNamespace()
482 || tc2.Class("OUString").Namespace("rtl").GlobalNamespace()))
484 return true;
486 report(DiagnosticsEngine::Warning,
487 "directly use a %0 value instead of a %select{_ostr|_ustr}1 user-defined string"
488 " literal",
489 expr->getExprLoc())
490 << t1.getUnqualifiedType() << bool(tc2.Class("OUString")) << expr->getSourceRange();
491 return true;
494 private:
495 bool isSpellingRange(SourceLocation loc1, SourceLocation loc2)
497 if (!SourceLocation::isPairOfFileLocations(loc1, loc2))
499 return false;
501 if (compiler.getSourceManager().getFileID(loc1)
502 != compiler.getSourceManager().getFileID(loc2))
504 return false;
506 return loc1 <= loc2;
509 unsigned delta(SourceLocation loc1, SourceLocation loc2)
511 return compiler.getSourceManager().getDecomposedLoc(loc2).second
512 - compiler.getSourceManager().getDecomposedLoc(loc1).second;
515 struct Var
517 bool multipleUses = false;
518 DeclRefExpr const* singleUse = nullptr;
519 std::set<CXXConstructExpr const*> explicitConversions;
522 std::set<Expr const*> defaultArgs_;
523 std::stack<CXXFunctionalCastExpr const*> functionalCasts_;
524 std::set<SourceLocation> locs_;
525 std::map<VarDecl const*, Var> vars_;
528 loplugin::Plugin::Registration<Ostr> X("ostr", true);
531 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */