1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
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/.
18 // Rewrite some uses of O[U]String to use ""_ostr/u""_ustr literals.
22 class Ostr
: public loplugin::FilteringRewritePlugin
<Ostr
>
25 explicit Ostr(loplugin::InstantiationData
const& data
)
26 : FilteringRewritePlugin(data
)
30 // Needed so that e.g.
32 // struct S { OUString s; };
36 bool shouldVisitImplicitCode() const { return true; }
40 if (compiler
.getLangOpts().CPlusPlus
41 && TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl()))
43 for (auto const& i
: vars_
)
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
65 || insertTextAfterToken(e
->getEndLoc(), utf16
? "_ustr" : "_ostr"))
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"))
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
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
));
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())
123 report(DiagnosticsEngine::Note
, "variable %0 declared here",
125 << d
->getName() << d
->getSourceRange();
131 if (!compiler
.getDiagnosticOpts().VerifyDiagnostics
)
133 //TODO, left for later:
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",
147 << d
->getName() << d
->getSourceRange();
154 bool TraverseParmVarDecl(ParmVarDecl
* decl
)
158 // struct S { void f(int = 0); };
161 // would visit the default argument twice:
162 if (decl
->hasDefaultArg() && !decl
->hasUninstantiatedDefaultArg()
163 && !decl
->hasUnparsedDefaultArg() && !defaultArgs_
.insert(decl
->getDefaultArg()).second
)
167 return RecursiveASTVisitor::TraverseParmVarDecl(decl
);
170 bool TraverseCXXFunctionalCastExpr(CXXFunctionalCastExpr
* expr
)
172 functionalCasts_
.push(expr
);
173 auto const ret
= RecursiveASTVisitor::TraverseCXXFunctionalCastExpr(expr
);
174 functionalCasts_
.pop();
178 bool VisitVarDecl(VarDecl
const* decl
)
180 if (ignoreLocation(decl
))
184 if (!decl
->isThisDeclarationADefinition())
188 loplugin::TypeCheck
const tc(decl
->getType());
189 if (!(tc
.Class("OStringLiteral").Namespace("rtl").GlobalNamespace()
190 || tc
.Class("OUStringLiteral").Namespace("rtl").GlobalNamespace()))
194 if (suppressWarningAt(decl
->getLocation()))
198 vars_
[decl
].multipleUses
199 = decl
->getDeclContext()->isFileContext()
200 ? !compiler
.getSourceManager().isInMainFile(decl
->getLocation())
201 : decl
->isExternallyVisible();
205 bool VisitDeclRefExpr(DeclRefExpr
const* expr
)
207 if (ignoreLocation(expr
))
211 auto const d1
= dyn_cast
<VarDecl
>(expr
->getDecl());
216 auto const d2
= d1
->getDefinition();
221 auto const i
= vars_
.find(d2
);
222 if (i
== vars_
.end())
226 if (!i
->second
.multipleUses
)
228 if (i
->second
.singleUse
== nullptr)
230 i
->second
.singleUse
= expr
;
234 i
->second
.multipleUses
= true;
235 i
->second
.singleUse
= nullptr;
241 bool VisitCXXConstructExpr(CXXConstructExpr
const* expr
)
243 if (ignoreLocation(expr
))
247 auto const dc
= expr
->getConstructor()->getParent();
249 = bool(loplugin::DeclCheck(dc
).Class("OUString").Namespace("rtl").GlobalNamespace());
250 if (!(utf16
|| loplugin::DeclCheck(dc
).Class("OString").Namespace("rtl").GlobalNamespace()))
254 if (expr
->getNumArgs() == 1
255 && loplugin::TypeCheck(expr
->getArg(0)->getType())
256 .Class(utf16
? "OUStringLiteral" : "OStringLiteral")
260 if (functionalCasts_
.empty()
261 || functionalCasts_
.top()->getSubExpr()->IgnoreImplicit() != expr
)
265 auto const e
= dyn_cast
<DeclRefExpr
>(expr
->getArg(0)->IgnoreParenImpCasts());
270 auto const d1
= dyn_cast
<VarDecl
>(e
->getDecl());
275 auto const d2
= d1
->getDefinition();
280 auto const i
= vars_
.find(d2
);
281 if (i
== vars_
.end())
285 i
->second
.explicitConversions
.insert(expr
);
288 if (expr
->getNumArgs() != 2)
292 if (!loplugin::TypeCheck(expr
->getArg(1)->getType())
294 .Namespace("libreoffice_internal")
300 auto const e2
= dyn_cast
<clang::StringLiteral
>(expr
->getArg(0)->IgnoreParenImpCasts());
305 if (!(compat::isOrdinary(e2
) || e2
->isUTF8()))
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
)
332 auto const macroBegin
= l2
.isMacroID()
333 && Lexer::isAtStartOfMacroExpansion(l2
, compiler
.getSourceManager(),
334 compiler
.getLangOpts());
337 l2
= compiler
.getSourceManager().getImmediateMacroCallerLoc(l2
);
339 auto const macroEnd
= l3
.isMacroID()
340 && Lexer::isAtEndOfMacroExpansion(l3
, compiler
.getSourceManager(),
341 compiler
.getLangOpts());
344 l3
= compiler
.getSourceManager().getImmediateMacroCallerLoc(l3
);
351 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(l1
)))
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):
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")))
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",
379 << utf16
<< expr
->getType().getLocalUnqualifiedType() << expr
->getSourceRange();
383 bool VisitCXXOperatorCallExpr(CXXOperatorCallExpr
const* expr
)
385 if (ignoreLocation(expr
))
389 if (expr
->getOperator() != OO_Equal
)
393 if (!loplugin::TypeCheck(expr
->getArg(0)->getType())
400 auto const e2
= dyn_cast
<clang::StringLiteral
>(expr
->getArg(1)->IgnoreParenImpCasts());
405 if (rewriter
!= nullptr)
407 auto loc
= e2
->getEndLoc();
408 auto const macroEnd
= loc
.isMacroID()
409 && Lexer::isAtEndOfMacroExpansion(
410 loc
, compiler
.getSourceManager(), compiler
.getLangOpts());
413 loc
= compiler
.getSourceManager().getImmediateMacroCallerLoc(loc
);
415 if (insertTextAfterToken(loc
, macroEnd
? " \"\"_ostr" : "_ostr"))
420 report(DiagnosticsEngine::Warning
,
421 "use a _ostr user-defined string literal instead of assigning from an ordinary"
424 << expr
->getSourceRange();
428 bool VisitCXXMemberCallExpr(CXXMemberCallExpr
const* expr
)
430 if (ignoreLocation(expr
))
434 if (!loplugin::DeclCheck(expr
->getMethodDecl()).Operator(OO_Equal
))
438 if (!loplugin::TypeCheck(expr
->getObjectType())
445 auto const e2
= dyn_cast
<clang::StringLiteral
>(expr
->getArg(0)->IgnoreParenImpCasts());
450 if (rewriter
!= nullptr)
454 report(DiagnosticsEngine::Warning
,
455 "use a _ostr user-defined string literal instead of assigning from an ordinary"
458 << expr
->getSourceRange();
462 bool VisitCastExpr(CastExpr
const* expr
)
464 if (ignoreLocation(expr
))
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()))
475 auto const e2
= dyn_cast
<UserDefinedLiteral
>(expr
->getSubExprAsWritten());
480 auto const tc2
= loplugin::TypeCheck(e2
->getType());
481 if (!(tc2
.Class("OString").Namespace("rtl").GlobalNamespace()
482 || tc2
.Class("OUString").Namespace("rtl").GlobalNamespace()))
486 report(DiagnosticsEngine::Warning
,
487 "directly use a %0 value instead of a %select{_ostr|_ustr}1 user-defined string"
490 << t1
.getUnqualifiedType() << bool(tc2
.Class("OUString")) << expr
->getSourceRange();
495 bool isSpellingRange(SourceLocation loc1
, SourceLocation loc2
)
497 if (!SourceLocation::isPairOfFileLocations(loc1
, loc2
))
501 if (compiler
.getSourceManager().getFileID(loc1
)
502 != compiler
.getSourceManager().getFileID(loc2
))
509 unsigned delta(SourceLocation loc1
, SourceLocation loc2
)
511 return compiler
.getSourceManager().getDecomposedLoc(loc2
).second
512 - compiler
.getSourceManager().getDecomposedLoc(loc1
).second
;
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: */