null-deref seen in testing
[LibreOffice.git] / compilerplugins / clang / bufferadd.cxx
blob8f58e46aba14ea2d8d09d4c031d8827703d0e2f5
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 */
9 #ifndef LO_CLANG_SHARED_PLUGINS
11 #include <cassert>
12 #include <string>
13 #include <iostream>
14 #include <unordered_set>
16 #include "plugin.hxx"
17 #include "check.hxx"
18 #include "compat.hxx"
19 #include "config_clang.h"
20 #include "clang/AST/CXXInheritance.h"
21 #include "clang/AST/StmtVisitor.h"
23 /**
24 Look for *StringBuffer append sequences which can be converted to *String + sequences.
27 namespace
29 class BufferAdd : public loplugin::FilteringPlugin<BufferAdd>
31 public:
32 explicit BufferAdd(loplugin::InstantiationData const& data)
33 : FilteringPlugin(data)
37 bool preRun() override
39 std::string fn(handler.getMainFileName());
40 loplugin::normalizeDotDotInFilePath(fn);
41 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/qa/rtl/oustring/"))
42 return false;
43 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/qa/rtl/oustringbuffer/"))
44 return false;
45 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/qa/rtl/strings/"))
46 return false;
47 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/qa/OStringBuffer/"))
48 return false;
49 // some false +
50 if (loplugin::isSamePathname(fn, SRCDIR "/unoidl/source/sourcetreeprovider.cxx"))
51 return false;
52 if (loplugin::isSamePathname(fn,
53 SRCDIR "/sw/source/writerfilter/dmapper/StyleSheetTable.cxx"))
54 return false;
55 if (loplugin::isSamePathname(fn,
56 SRCDIR "/sw/source/writerfilter/dmapper/GraphicImport.cxx"))
57 return false;
58 if (loplugin::isSamePathname(fn, SRCDIR "/sdext/source/pdfimport/pdfparse/pdfparse.cxx"))
59 return false;
60 return true;
63 void postRun() override
65 for (auto const& pair : goodMap)
66 if (!isa<ParmVarDecl>(pair.first) &&
67 // reference types have slightly weird behaviour
68 !pair.first->getType()->isReferenceType()
69 && badMap.find(pair.first) == badMap.end())
70 report(DiagnosticsEngine::Warning,
71 "convert this append sequence into a *String + sequence",
72 pair.first->getBeginLoc())
73 << pair.first->getSourceRange();
76 virtual void run() override
78 if (!preRun())
79 return;
80 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
81 postRun();
84 bool VisitStmt(Stmt const*);
85 bool VisitCallExpr(CallExpr const*);
86 bool VisitCXXConstructExpr(CXXConstructExpr const*);
87 bool VisitUnaryOperator(UnaryOperator const*);
89 private:
90 void findBufferAssignOrAdd(const Stmt* parentStmt, Stmt const*);
91 Expr const* ignore(Expr const*);
92 bool isSideEffectFree(Expr const*);
93 bool isMethodOkToMerge(CXXMemberCallExpr const*);
94 void addToGoodMap(const VarDecl* varDecl, const Stmt* parentStmt);
96 std::unordered_map<const VarDecl*, const Stmt*> goodMap;
97 std::unordered_set<const VarDecl*> badMap;
100 bool BufferAdd::VisitStmt(Stmt const* stmt)
102 if (ignoreLocation(stmt))
103 return true;
105 if (!isa<CompoundStmt>(stmt) && !isa<CXXCatchStmt>(stmt) && !isa<CXXForRangeStmt>(stmt)
106 && !isa<CXXTryStmt>(stmt) && !isa<DoStmt>(stmt) && !isa<ForStmt>(stmt) && !isa<IfStmt>(stmt)
107 && !isa<SwitchStmt>(stmt) && !isa<WhileStmt>(stmt))
108 return true;
110 for (auto it = stmt->child_begin(); it != stmt->child_end(); ++it)
111 if (*it)
112 findBufferAssignOrAdd(stmt, *it);
114 return true;
117 bool BufferAdd::VisitCallExpr(CallExpr const* callExpr)
119 if (ignoreLocation(callExpr))
120 return true;
122 for (unsigned i = 0; i != callExpr->getNumArgs(); ++i)
124 auto a = ignore(callExpr->getArg(i));
125 if (auto declRefExpr = dyn_cast<DeclRefExpr>(a))
126 if (auto varDecl = dyn_cast<VarDecl>(declRefExpr->getDecl()))
127 badMap.insert(varDecl);
129 return true;
132 bool BufferAdd::VisitCXXConstructExpr(CXXConstructExpr const* callExpr)
134 if (ignoreLocation(callExpr))
135 return true;
137 for (unsigned i = 0; i != callExpr->getNumArgs(); ++i)
139 auto a = ignore(callExpr->getArg(i));
140 if (auto declRefExpr = dyn_cast<DeclRefExpr>(a))
141 if (auto varDecl = dyn_cast<VarDecl>(declRefExpr->getDecl()))
142 badMap.insert(varDecl);
144 return true;
147 bool BufferAdd::VisitUnaryOperator(const UnaryOperator* unaryOp)
149 if (ignoreLocation(unaryOp))
150 return true;
151 if (unaryOp->getOpcode() != UO_AddrOf)
152 return true;
153 auto a = ignore(unaryOp->getSubExpr());
154 if (auto declRefExpr = dyn_cast<DeclRefExpr>(a))
155 if (auto varDecl = dyn_cast<VarDecl>(declRefExpr->getDecl()))
156 badMap.insert(varDecl);
157 return true;
160 void BufferAdd::findBufferAssignOrAdd(const Stmt* parentStmt, Stmt const* stmt)
162 if (auto exprCleanup = dyn_cast<ExprWithCleanups>(stmt))
163 stmt = exprCleanup->getSubExpr();
164 if (auto switchCase = dyn_cast<SwitchCase>(stmt))
165 stmt = switchCase->getSubStmt();
166 if (auto declStmt = dyn_cast<DeclStmt>(stmt))
168 if (declStmt->isSingleDecl())
169 if (auto varDeclLHS = dyn_cast_or_null<VarDecl>(declStmt->getSingleDecl()))
171 auto tc = loplugin::TypeCheck(varDeclLHS->getType());
172 if (!tc.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
173 && !tc.Class("OStringBuffer").Namespace("rtl").GlobalNamespace())
174 return;
175 if (varDeclLHS->getStorageDuration() == SD_Static)
176 return;
177 if (!varDeclLHS->hasInit())
178 return;
179 auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(ignore(varDeclLHS->getInit()));
180 if (cxxConstructExpr)
182 addToGoodMap(varDeclLHS, parentStmt);
183 return;
185 if (!isSideEffectFree(varDeclLHS->getInit()))
186 badMap.insert(varDeclLHS);
187 else
188 addToGoodMap(varDeclLHS, parentStmt);
190 return;
193 // check for single calls to buffer method
195 if (auto memberCallExpr = dyn_cast<CXXMemberCallExpr>(stmt))
197 if (auto declRefExprLHS
198 = dyn_cast<DeclRefExpr>(ignore(memberCallExpr->getImplicitObjectArgument())))
200 auto methodDecl = memberCallExpr->getMethodDecl();
201 if (methodDecl && methodDecl->getIdentifier())
202 if (auto varDeclLHS = dyn_cast<VarDecl>(declRefExprLHS->getDecl()))
204 auto tc = loplugin::TypeCheck(varDeclLHS->getType());
205 if (tc.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
206 || tc.Class("OStringBuffer").Namespace("rtl").GlobalNamespace())
208 if (isMethodOkToMerge(memberCallExpr))
209 addToGoodMap(varDeclLHS, parentStmt);
210 else
211 badMap.insert(varDeclLHS);
214 return;
218 // now check for chained append calls
220 auto expr = dyn_cast<Expr>(stmt);
221 if (!expr)
222 return;
223 auto tc = loplugin::TypeCheck(expr->getType());
224 if (!tc.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
225 && !tc.Class("OStringBuffer").Namespace("rtl").GlobalNamespace())
226 return;
228 // unwrap the chain (which runs from right to left)
229 const VarDecl* varDeclLHS = nullptr;
230 bool good = true;
231 while (true)
233 auto memberCallExpr = dyn_cast<CXXMemberCallExpr>(expr);
234 if (!memberCallExpr)
235 break;
236 good &= isMethodOkToMerge(memberCallExpr);
238 if (auto declRefExprLHS
239 = dyn_cast<DeclRefExpr>(ignore(memberCallExpr->getImplicitObjectArgument())))
241 varDeclLHS = dyn_cast<VarDecl>(declRefExprLHS->getDecl());
242 break;
244 expr = memberCallExpr->getImplicitObjectArgument();
247 if (varDeclLHS)
249 if (good)
250 addToGoodMap(varDeclLHS, parentStmt);
251 else
252 badMap.insert(varDeclLHS);
256 void BufferAdd::addToGoodMap(const VarDecl* varDecl, const Stmt* parentStmt)
258 // check that vars are all inside the same compoundstmt, if they are not, we cannot combine them
259 auto it = goodMap.find(varDecl);
260 if (it != goodMap.end())
262 if (it->second == parentStmt)
263 return;
264 // don't treat these as parents, otherwise we eliminate .append.append sequences
265 if (isa<MemberExpr>(parentStmt))
266 return;
267 if (isa<CXXMemberCallExpr>(parentStmt))
268 return;
269 badMap.insert(varDecl);
271 else
272 goodMap.emplace(varDecl, parentStmt);
275 bool BufferAdd::isMethodOkToMerge(CXXMemberCallExpr const* memberCall)
277 auto methodDecl = memberCall->getMethodDecl();
278 if (methodDecl->getNumParams() == 0)
279 return true;
281 if (auto const id = methodDecl->getIdentifier())
283 auto name = id->getName();
284 if (name == "appendUninitialized" || name == "setLength" || name == "remove"
285 || name == "insert" || name == "appendAscii" || name == "appendUtf32")
286 return false;
289 auto rhs = memberCall->getArg(0);
290 if (!isSideEffectFree(rhs))
291 return false;
292 return true;
295 Expr const* BufferAdd::ignore(Expr const* expr)
297 return expr->IgnoreImplicit()->IgnoreParens()->IgnoreImplicit();
300 bool BufferAdd::isSideEffectFree(Expr const* expr)
302 expr = ignore(expr);
303 // I don't think the OUStringAppend functionality can handle this efficiently
304 if (isa<ConditionalOperator>(expr))
305 return false;
306 // Multiple statements have a well defined evaluation order (sequence points between them)
307 // but a single expression may be evaluated in arbitrary order;
308 // if there are side effects in one of the sub-expressions that have an effect on another subexpression,
309 // the result may be incorrect, and you don't necessarily notice in tests because the order is compiler-dependent.
310 // for example see commit afd743141f7a7dd05914d0872c9afe079f16fe0c where such a refactoring introduced such a bug.
311 // So only consider simple RHS expressions.
312 if (!expr->HasSideEffects(compiler.getASTContext()))
313 return true;
315 // check for chained adds which are side-effect free
316 if (auto operatorCall = dyn_cast<CXXOperatorCallExpr>(expr))
318 auto op = operatorCall->getOperator();
319 if (op == OO_PlusEqual || op == OO_Plus)
320 if (isSideEffectFree(operatorCall->getArg(0))
321 && isSideEffectFree(operatorCall->getArg(1)))
322 return true;
325 if (auto callExpr = dyn_cast<CallExpr>(expr))
327 // check for calls through OUString::number/OUString::unacquired
328 if (auto calleeMethodDecl = dyn_cast_or_null<CXXMethodDecl>(callExpr->getCalleeDecl()))
329 if (calleeMethodDecl && calleeMethodDecl->getIdentifier())
331 if (callExpr->getNumArgs() > 0)
333 auto tc = loplugin::TypeCheck(calleeMethodDecl->getParent());
334 if (tc.Class("OUString") || tc.Class("OString"))
336 if (isSideEffectFree(callExpr->getArg(0)))
337 return true;
341 if (auto calleeFunctionDecl = dyn_cast_or_null<FunctionDecl>(callExpr->getCalleeDecl()))
342 if (calleeFunctionDecl && calleeFunctionDecl->getIdentifier())
344 auto name = calleeFunctionDecl->getName();
345 // check for calls through OUStringToOString
346 if (name == "OUStringToOString" || name == "OStringToOUString")
347 if (isSideEffectFree(callExpr->getArg(0)))
348 return true;
349 // allowlist some known-safe methods
350 if (compat::ends_with(name, "ResId") || name == "GetXMLToken")
351 if (isSideEffectFree(callExpr->getArg(0)))
352 return true;
354 // O[U]String::operator std::[u16]string_view:
355 if (auto const d = dyn_cast_or_null<CXXConversionDecl>(callExpr->getCalleeDecl()))
357 auto tc = loplugin::TypeCheck(d->getParent());
358 if (tc.Class("OString") || tc.Class("OUString"))
360 return true;
365 // sometimes we have a constructor call on the RHS
366 if (auto constructExpr = dyn_cast<CXXConstructExpr>(expr))
368 auto dc = loplugin::DeclCheck(constructExpr->getConstructor());
369 if (dc.MemberFunction().Class("OUString") || dc.MemberFunction().Class("OString")
370 || dc.MemberFunction().Class("OUStringBuffer")
371 || dc.MemberFunction().Class("OStringBuffer"))
372 if (constructExpr->getNumArgs() == 0 || isSideEffectFree(constructExpr->getArg(0)))
373 return true;
374 // Expr::HasSideEffects does not like stuff that passes through OUStringLiteral
375 auto dc2 = loplugin::DeclCheck(constructExpr->getConstructor()->getParent());
376 if (dc2.Class("OUStringLiteral").Namespace("rtl").GlobalNamespace())
377 return true;
380 // when adding literals, we sometimes get this
381 if (auto functionalCastExpr = dyn_cast<CXXFunctionalCastExpr>(expr))
383 auto tc = loplugin::TypeCheck(functionalCastExpr->getType());
384 if (tc.Class("OUStringLiteral").Namespace("rtl").GlobalNamespace())
385 return isSideEffectFree(functionalCastExpr->getSubExpr());
388 return false;
391 loplugin::Plugin::Registration<BufferAdd> bufferadd("bufferadd");
394 #endif // LO_CLANG_SHARED_PLUGINS
396 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */