Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / compilerplugins / clang / stringliteralvar.cxx
blobfcd3690669e711c70922d5840b1eb2e9620c3f4a
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 // 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
26 // begin with.
28 #include <cassert>
30 #include "check.hxx"
31 #include "compat.hxx"
32 #include "plugin.hxx"
34 namespace
36 bool isAutomaticVariable(VarDecl const* decl)
38 switch (cast<VarDecl>(decl)->getStorageDuration())
40 case SD_Automatic:
41 return true;
42 case SD_Thread:
43 case SD_Static:
44 return false;
45 case SD_FullExpression:
46 case SD_Dynamic:
47 assert(false);
48 default:
49 llvm_unreachable("unknown StorageDuration");
53 class StringLiteralVar final : public loplugin::FilteringPlugin<StringLiteralVar>
55 public:
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))
72 return true;
74 loplugin::TypeCheck const tc(expr->getType());
75 if (!(tc.Class("OString").Namespace("rtl").GlobalNamespace()
76 || tc.Class("OUString").Namespace("rtl").GlobalNamespace()))
78 return true;
80 auto const ctor = expr->getConstructor();
81 switch (ctor->getNumParams())
83 case 1:
85 auto const e = dyn_cast<DeclRefExpr>(expr->getArg(0)->IgnoreParenImpCasts());
86 if (e == nullptr)
88 return true;
90 auto const tc = loplugin::TypeCheck(e->getType());
91 if (!(tc.Class("OStringLiteral").Namespace("rtl").GlobalNamespace()
92 || tc.Class("OUStringLiteral").Namespace("rtl").GlobalNamespace()))
94 return true;
96 auto const d = e->getDecl();
97 if (!isAutomaticVariable(cast<VarDecl>(d)))
99 return true;
101 if (!reportedAutomatic_.insert(d).second)
103 return true;
105 report(DiagnosticsEngine::Warning,
106 "variable %0 of type %1 with automatic storage duration most likely needs "
107 "to be static",
108 d->getLocation())
109 << d << d->getType() << d->getSourceRange();
110 report(DiagnosticsEngine::Note, "first converted to %0 here", expr->getLocation())
111 << expr->getType() << expr->getSourceRange();
113 break;
114 case 2:
116 auto const e1 = dyn_cast<DeclRefExpr>(expr->getArg(0)->IgnoreParenImpCasts());
117 if (e1 == nullptr)
119 return true;
121 auto const t = e1->getType();
122 if (!(t.isConstQualified() && t->isConstantArrayType()))
124 return true;
126 auto const e2 = expr->getArg(1);
127 if (!((isa<CXXDefaultArgExpr>(e2)
128 && loplugin::TypeCheck(e2->getType())
129 .Struct("Dummy")
130 .Namespace("libreoffice_internal")
131 .Namespace("rtl")
132 .GlobalNamespace())
133 || (loplugin::TypeCheck(ctor->getParamDecl(1)->getType())
134 .Typedef("sal_Int32")
135 .GlobalNamespace()
136 && e2->isIntegerConstantExpr(compiler.getASTContext()))))
138 return true;
140 auto const d = e1->getDecl();
141 if (isPotentiallyInitializedWithMalformedUtf16(d))
143 return true;
145 if (!reportedArray_.insert(d).second)
147 return true;
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",
152 d->getLocation())
153 << d << d->getType()
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",
157 expr->getLocation())
158 << expr->getType().getUnqualifiedType() << expr->getSourceRange();
160 break;
162 return true;
165 bool VisitCXXOperatorCallExpr(CXXOperatorCallExpr const* expr)
167 if (ignoreLocation(expr))
169 return true;
171 if (expr->getOperator() != OO_Equal)
173 return true;
175 loplugin::TypeCheck const tc(expr->getType());
176 if (!(tc.Class("OString").Namespace("rtl").GlobalNamespace()
177 || tc.Class("OUString").Namespace("rtl").GlobalNamespace()))
179 return true;
181 if (expr->getNumArgs() != 2)
183 return true;
185 auto const e = dyn_cast<DeclRefExpr>(expr->getArg(1)->IgnoreParenImpCasts());
186 if (e == nullptr)
188 return true;
190 auto const t = e->getType();
191 if (!(t.isConstQualified() && t->isConstantArrayType()))
193 return true;
195 auto const d = e->getDecl();
196 if (isPotentiallyInitializedWithMalformedUtf16(d))
198 return true;
200 if (!reportedArray_.insert(d).second)
202 return true;
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",
207 d->getLocation())
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();
212 return true;
215 bool VisitUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr const* expr)
217 if (ignoreLocation(expr))
219 return true;
221 if (expr->getKind() != UETT_SizeOf)
223 return true;
225 if (expr->isArgumentType())
227 return true;
229 auto const e = dyn_cast<DeclRefExpr>(expr->getArgumentExpr()->IgnoreParenImpCasts());
230 if (e == nullptr)
232 return true;
234 auto const tc = loplugin::TypeCheck(e->getType());
235 if (!(tc.Class("OStringLiteral").Namespace("rtl").GlobalNamespace()
236 || tc.Class("OUStringLiteral").Namespace("rtl").GlobalNamespace()))
238 return true;
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();
244 return true;
247 bool preRun() override { return compiler.getLangOpts().CPlusPlus; }
249 private:
250 void run() override
252 if (preRun())
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())
267 return false;
269 auto const init = cast<VarDecl>(decl)->getAnyInitializer();
270 if (init == nullptr)
272 return true;
274 auto const list = dyn_cast<InitListExpr>(init);
275 if (list == nullptr)
277 // Assuming that the initializer already is a string literal, assume that that string
278 // literal has no issues with malformed UTF-16:
279 if (isDebugMode())
281 assert(isa<clang::StringLiteral>(init));
283 return false;
285 auto highSurrogate = false;
286 for (auto const e : list->inits())
288 llvm::APSInt v;
289 if (!compat::EvaluateAsInt(e, v, compiler.getASTContext()))
291 return true;
293 if (highSurrogate)
295 if (v < 0xDC00 || v > 0xDFFF)
297 return true;
299 highSurrogate = false;
301 else if (v >= 0xD800 && v <= 0xDBFF)
303 highSurrogate = true;
305 else if (v >= 0xDC00 && v <= 0xDFFF)
307 return true;
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: */