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/.
10 // Find constant character array variables that are either
11 // (a) passed into O[U]String constructors
12 // (b) assigned to O[U]String
13 // and should thus be turned into O[U]StringLiteral variables.
15 // Such a variable may have been used in multiple places, not all of which would be compatible with
16 // changing the variable's type to O[U]StringLiteral. However, this plugin is aggressive and
17 // ignores all but the first use of such a variable. In all cases of incompatible uses so far, it
18 // was possible to change to surrounding code (for the better) to make the changes work.
20 // The plugin also flags O[U]StringLiteral variables of automatic storage duration, and uses of such
21 // variables with sizeof---two likely errors that can occur once a variable has been changed from a
22 // character array to O[U]StringLiteral.
24 //TODO: In theory, we should not only look for variables, but also for non-static data members. In
25 // practice, those should be rare, though, as they should arguably have been static data members to
36 bool isAutomaticVariable(VarDecl
const* decl
)
38 switch (cast
<VarDecl
>(decl
)->getStorageDuration())
45 case SD_FullExpression
:
49 llvm_unreachable("unknown StorageDuration");
53 class StringLiteralVar final
: public loplugin::FilteringPlugin
<StringLiteralVar
>
56 explicit StringLiteralVar(loplugin::InstantiationData
const& data
)
57 : FilteringPlugin(data
)
61 bool TraverseInitListExpr(InitListExpr
* expr
, DataRecursionQueue
* queue
= nullptr)
63 return WalkUpFromInitListExpr(expr
)
64 && TraverseSynOrSemInitListExpr(
65 expr
->isSemanticForm() ? expr
: expr
->getSemanticForm(), queue
);
68 bool VisitCXXConstructExpr(CXXConstructExpr
const* expr
)
70 if (ignoreLocation(expr
))
74 loplugin::TypeCheck
const tc(expr
->getType());
75 if (!(tc
.Class("OString").Namespace("rtl").GlobalNamespace()
76 || tc
.Class("OUString").Namespace("rtl").GlobalNamespace()))
80 auto const ctor
= expr
->getConstructor();
81 switch (ctor
->getNumParams())
85 auto const e
= dyn_cast
<DeclRefExpr
>(expr
->getArg(0)->IgnoreParenImpCasts());
90 auto const tc
= loplugin::TypeCheck(e
->getType());
91 if (!(tc
.Class("OStringLiteral").Namespace("rtl").GlobalNamespace()
92 || tc
.Class("OUStringLiteral").Namespace("rtl").GlobalNamespace()))
96 auto const d
= e
->getDecl();
97 if (!isAutomaticVariable(cast
<VarDecl
>(d
)))
101 if (!reportedAutomatic_
.insert(d
).second
)
105 report(DiagnosticsEngine::Warning
,
106 "variable %0 of type %1 with automatic storage duration most likely needs "
109 << d
<< d
->getType() << d
->getSourceRange();
110 report(DiagnosticsEngine::Note
, "first converted to %0 here", expr
->getLocation())
111 << expr
->getType() << expr
->getSourceRange();
116 auto const e1
= dyn_cast
<DeclRefExpr
>(expr
->getArg(0)->IgnoreParenImpCasts());
121 auto const t
= e1
->getType();
122 if (!(t
.isConstQualified() && t
->isConstantArrayType()))
126 auto const e2
= expr
->getArg(1);
127 if (!((isa
<CXXDefaultArgExpr
>(e2
)
128 && loplugin::TypeCheck(e2
->getType())
130 .Namespace("libreoffice_internal")
133 || (loplugin::TypeCheck(ctor
->getParamDecl(1)->getType())
134 .Typedef("sal_Int32")
136 && e2
->isIntegerConstantExpr(compiler
.getASTContext()))))
140 auto const d
= e1
->getDecl();
141 if (isPotentiallyInitializedWithMalformedUtf16(d
))
145 if (!reportedArray_
.insert(d
).second
)
149 report(DiagnosticsEngine::Warning
,
150 "change type of variable %0 from constant character array (%1) to "
151 "%select{OStringLiteral|OUStringLiteral}2%select{|, and make it static}3",
154 << (tc
.Class("OString").Namespace("rtl").GlobalNamespace() ? 0 : 1)
155 << isAutomaticVariable(cast
<VarDecl
>(d
)) << d
->getSourceRange();
156 report(DiagnosticsEngine::Note
, "first passed into a %0 constructor here",
158 << expr
->getType().getUnqualifiedType() << expr
->getSourceRange();
165 bool VisitCXXOperatorCallExpr(CXXOperatorCallExpr
const* expr
)
167 if (ignoreLocation(expr
))
171 if (expr
->getOperator() != OO_Equal
)
175 loplugin::TypeCheck
const tc(expr
->getType());
176 if (!(tc
.Class("OString").Namespace("rtl").GlobalNamespace()
177 || tc
.Class("OUString").Namespace("rtl").GlobalNamespace()))
181 if (expr
->getNumArgs() != 2)
185 auto const e
= dyn_cast
<DeclRefExpr
>(expr
->getArg(1)->IgnoreParenImpCasts());
190 auto const t
= e
->getType();
191 if (!(t
.isConstQualified() && t
->isConstantArrayType()))
195 auto const d
= e
->getDecl();
196 if (isPotentiallyInitializedWithMalformedUtf16(d
))
200 if (!reportedArray_
.insert(d
).second
)
204 report(DiagnosticsEngine::Warning
,
205 "change type of variable %0 from constant character array (%1) to "
206 "%select{OStringLiteral|OUStringLiteral}2%select{|, and make it static}3",
208 << d
<< d
->getType() << (tc
.Class("OString").Namespace("rtl").GlobalNamespace() ? 0 : 1)
209 << isAutomaticVariable(cast
<VarDecl
>(d
)) << d
->getSourceRange();
210 report(DiagnosticsEngine::Note
, "first assigned here", expr
->getBeginLoc())
211 << expr
->getSourceRange();
215 bool VisitUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr
const* expr
)
217 if (ignoreLocation(expr
))
221 if (expr
->getKind() != UETT_SizeOf
)
225 if (expr
->isArgumentType())
229 auto const e
= dyn_cast
<DeclRefExpr
>(expr
->getArgumentExpr()->IgnoreParenImpCasts());
234 auto const tc
= loplugin::TypeCheck(e
->getType());
235 if (!(tc
.Class("OStringLiteral").Namespace("rtl").GlobalNamespace()
236 || tc
.Class("OUStringLiteral").Namespace("rtl").GlobalNamespace()))
240 auto const d
= e
->getDecl();
241 report(DiagnosticsEngine::Warning
,
242 "variable %0 of type %1 suspiciously used in a sizeof expression", e
->getLocation())
243 << d
<< d
->getType() << expr
->getSourceRange();
247 bool preRun() override
{ return compiler
.getLangOpts().CPlusPlus
; }
254 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
258 // There is some confusion on the semantics of numeric-escape-sequences in string literals, see
259 // <https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2029r4.html> "Proposed resolution
260 // for core issues 411, 1656, and 2333; numeric and universal character escapes in character and
261 // string literals", so suppress warnings about arrays that are deliberately not written as
262 // UTF-16 string literals because they contain lone surrogates:
263 bool isPotentiallyInitializedWithMalformedUtf16(ValueDecl
const* decl
) const
265 if (!decl
->getType()->getArrayElementTypeNoTypeQual()->isChar16Type())
269 auto const init
= cast
<VarDecl
>(decl
)->getAnyInitializer();
274 auto const list
= dyn_cast
<InitListExpr
>(init
);
277 // Assuming that the initializer already is a string literal, assume that that string
278 // literal has no issues with malformed UTF-16:
281 assert(isa
<clang::StringLiteral
>(init
));
285 auto highSurrogate
= false;
286 for (auto const e
: list
->inits())
289 if (!compat::EvaluateAsInt(e
, v
, compiler
.getASTContext()))
295 if (v
< 0xDC00 || v
> 0xDFFF)
299 highSurrogate
= false;
301 else if (v
>= 0xD800 && v
<= 0xDBFF)
303 highSurrogate
= true;
305 else if (v
>= 0xDC00 && v
<= 0xDFFF)
310 return highSurrogate
;
313 std::set
<Decl
const*> reportedAutomatic_
;
314 std::set
<Decl
const*> reportedArray_
;
317 static loplugin::Plugin::Registration
<StringLiteralVar
> reg("stringliteralvar");
320 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */